nookara 0.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nookara-0.8.0/LICENSE +21 -0
- nookara-0.8.0/PKG-INFO +574 -0
- nookara-0.8.0/README.md +554 -0
- nookara-0.8.0/nookara/__init__.py +3 -0
- nookara-0.8.0/nookara.egg-info/PKG-INFO +574 -0
- nookara-0.8.0/nookara.egg-info/SOURCES.txt +27 -0
- nookara-0.8.0/nookara.egg-info/dependency_links.txt +1 -0
- nookara-0.8.0/nookara.egg-info/requires.txt +10 -0
- nookara-0.8.0/nookara.egg-info/top_level.txt +2 -0
- nookara-0.8.0/pyproject.toml +28 -0
- nookara-0.8.0/setup.cfg +4 -0
- nookara-0.8.0/velora_api/__init__.py +24 -0
- nookara-0.8.0/velora_api/_origin.py +19 -0
- nookara-0.8.0/velora_api/_version.py +1 -0
- nookara-0.8.0/velora_api/bonfire.py +111 -0
- nookara-0.8.0/velora_api/botpayments.py +220 -0
- nookara-0.8.0/velora_api/botprogram.py +186 -0
- nookara-0.8.0/velora_api/catalogue.json +19860 -0
- nookara-0.8.0/velora_api/client.py +735 -0
- nookara-0.8.0/velora_api/e2ee.py +280 -0
- nookara-0.8.0/velora_api/endpoints.py +2795 -0
- nookara-0.8.0/velora_api/instance.py +733 -0
- nookara-0.8.0/velora_api/instance_catalogue.json +2143 -0
- nookara-0.8.0/velora_api/instance_endpoints.py +686 -0
- nookara-0.8.0/velora_api/instance_gateway.py +331 -0
- nookara-0.8.0/velora_api/py.typed +0 -0
- nookara-0.8.0/velora_api/realtime.py +168 -0
- nookara-0.8.0/velora_api/scopes.py +100 -0
- nookara-0.8.0/velora_api/signing.py +160 -0
nookara-0.8.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ikelene
|
|
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.
|
nookara-0.8.0/PKG-INFO
ADDED
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nookara
|
|
3
|
+
Version: 0.8.0
|
|
4
|
+
Summary: Python SDK for Nookara: sign-in, profiles, friends, chat, credits and payments, storage, and bots.
|
|
5
|
+
Author: Ikelene
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://nookara.cc
|
|
8
|
+
Project-URL: Documentation, https://nookara.cc/guides/velora-api
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Provides-Extra: realtime
|
|
13
|
+
Requires-Dist: websockets<18,>=14; extra == "realtime"
|
|
14
|
+
Provides-Extra: signing
|
|
15
|
+
Requires-Dist: cryptography>=42; extra == "signing"
|
|
16
|
+
Provides-Extra: all
|
|
17
|
+
Requires-Dist: websockets<18,>=14; extra == "all"
|
|
18
|
+
Requires-Dist: cryptography>=42; extra == "all"
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# Nookara for Python
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
pip install nookara
|
|
25
|
+
pip install "nookara[realtime]" # websockets
|
|
26
|
+
pip install "nookara[signing]" # cryptography, for signed messages and encrypted DMs
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from nookara import Client, login, scope_mask
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`import velora_api` keeps working for older code, and the examples below still use that name.
|
|
34
|
+
|
|
35
|
+
**What's in it:** sign-in, profiles, friends, DMs, group chats, notifications, storage, credits, Ember chat and bots, bot payments and carts, Products, cart and direct pay, claims and reports, Nookara Drive, Forums, Devices and Apps, as one named method per route.
|
|
36
|
+
|
|
37
|
+
**Who can call what.** Some routes only work for a website sign-in or one of Nookara's own apps, and the server refuses anything else. Each method's entry in `catalogue.json` has a `surface`:
|
|
38
|
+
|
|
39
|
+
| surface | Who can call it |
|
|
40
|
+
|---|---|
|
|
41
|
+
| (none) | Follows the route's own auth and scopes, the normal case for your App |
|
|
42
|
+
| `any` | Any signed-in token: `add_to_cart`, `product_access` |
|
|
43
|
+
| `app` | An App with Transfer Credits: `quote_directly`, `pay_directly`. Up to 100 credits a day per App per person |
|
|
44
|
+
| `website` | Website sign-in only: your Products, sales, purchases, reading the cart, paying at checkout, reports and claims, managing Apps. Listed so the reference is complete, your App can't call them |
|
|
45
|
+
| `drive` | Nookara Drive's own app only, or Nookara for Windows |
|
|
46
|
+
| `forums` | Nookara Forums' own app only, for anything that changes something. Reading needs no token |
|
|
47
|
+
| `devices` | Nookara for Windows or a website sign-in |
|
|
48
|
+
|
|
49
|
+
To charge someone from your App, send them to checkout with `add_to_cart`, or use `pay_directly` if they gave you Transfer Credits.
|
|
50
|
+
|
|
51
|
+
## About this client
|
|
52
|
+
|
|
53
|
+
Nookara is the identity and platform API: accounts, direct messages, groups, friends,
|
|
54
|
+
credits, instance tickets, signing keys and hosting. **Bonfire** is the chat system. Every
|
|
55
|
+
Bonfire server is an **instance** at its own web address (the main community is
|
|
56
|
+
`https://chat.ikelene.net`; Nookara-hosted ones are `https://<slug>-bonfire.ikelene.net`;
|
|
57
|
+
you can also self-host). One instance holds one community. This package talks to both.
|
|
58
|
+
|
|
59
|
+
It supports Python 3.10+ and exposes every documented REST operation as a named
|
|
60
|
+
method, plus a generic request method. REST needs only the Python standard
|
|
61
|
+
library. The optional `realtime` extra installs `websockets`; the optional
|
|
62
|
+
`signing` extra installs `cryptography` for message signing and verification.
|
|
63
|
+
|
|
64
|
+
## Install
|
|
65
|
+
|
|
66
|
+
Use the commands at the top of this page. From a source checkout, run `python -m pip install ".[realtime]"`
|
|
67
|
+
inside the folder containing `pyproject.toml`. `requirements.txt` installs the
|
|
68
|
+
optional websocket dependency only, not the Nookara package itself.
|
|
69
|
+
|
|
70
|
+
## Sign in, and what scopes mean now
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from velora_api import Client, login, scope_mask
|
|
74
|
+
|
|
75
|
+
token = login(project_name="My Python client", scopes=scope_mask("PROFILE_READ", "CHAT"))
|
|
76
|
+
velora = Client(token)
|
|
77
|
+
print(velora.scope_names()) # what the person actually allowed
|
|
78
|
+
print(velora.bonfire_instances()) # the Bonfire servers saved on this account
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The browser shows a consent screen. **Scopes are requested, never required**: the person
|
|
82
|
+
can untick any of them (the risky ones start unticked), so a token can come back with
|
|
83
|
+
fewer scopes than you asked for. Ask only for what you use, then check:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
velora.has_scope("MEDIA_UPLOAD") # True / False (names, labels such as "Upload Media", or bits)
|
|
87
|
+
velora.granted_scopes() # the usable mask (approved and not switched off by an admin)
|
|
88
|
+
velora.require_scope("CHAT") # raises ScopeError with a clear message
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
A call without the scope raises `ScopeError` (a subclass of `APIError`) with `scope`,
|
|
92
|
+
`label` and `disabled_by_admin`, so you can show "log in again to use this" instead of
|
|
93
|
+
crashing. `login()` never asks for a password. Project names that start with
|
|
94
|
+
`Bonfire (` are reserved for the official apps. `optional_scopes=` is still accepted
|
|
95
|
+
and merged into `scopes`. To print the URL yourself use `login(..., on_url=print)`.
|
|
96
|
+
Do not print the returned token. For an existing token use `Client(os.environ["VELORA_TOKEN"])`.
|
|
97
|
+
|
|
98
|
+
| Scope | Bit | Default on the consent screen | Bonfire instance scope |
|
|
99
|
+
|---|---|---|---|
|
|
100
|
+
| View Basic Profile | 1 | on | |
|
|
101
|
+
| View Sensitive Info | 2 | off | |
|
|
102
|
+
| Project Auth | 4 | on | |
|
|
103
|
+
| Modify Settings | 8 | off | |
|
|
104
|
+
| Read Storage / Write Storage | 16 / 32 | on / off | |
|
|
105
|
+
| Read Instant Messages / Send Instant Messages | 64 / 128 | on | |
|
|
106
|
+
| View Friends / Manage Friends | 256 / 512 | on / off | |
|
|
107
|
+
| Spend Credits | 1024 | off | |
|
|
108
|
+
| Chat Messaging | 2048 | on | `chat` (needed to sign in to an instance) |
|
|
109
|
+
| Upload Media | 4096 | on | `media` (uploads) |
|
|
110
|
+
| View Subscriptions | 8192 | on | |
|
|
111
|
+
| Nookara Voice | 16384 | on | `voice` |
|
|
112
|
+
| Sign Messages | 32768 | off | `sign` (signed messages) |
|
|
113
|
+
|
|
114
|
+
`velora_api.SCOPES`, `SCOPE_LABELS`, `DEFAULT_OFF`, `INSTANCE_SCOPES`,
|
|
115
|
+
`scope_mask()`, `scope_names()` and `has_scope()` give you the same table in code.
|
|
116
|
+
|
|
117
|
+
## Bonfire servers (instances)
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from velora_api import Client, Instance
|
|
121
|
+
|
|
122
|
+
velora = Client(token) # needs Chat (2048)
|
|
123
|
+
chat = Instance("https://chat.ikelene.net")
|
|
124
|
+
info = chat.discover() # /.well-known/bonfire, no login
|
|
125
|
+
print(info["name"], info["accounts_mode"], info["build"]["version"], info["read_only"])
|
|
126
|
+
chat.login_with_velora(velora, sign="auto") # Nookara-login instances
|
|
127
|
+
# chat.login("name", "password") # local-account instances
|
|
128
|
+
# chat.register("name", "password")
|
|
129
|
+
|
|
130
|
+
print(chat.scopes) # instance scopes of this session: chat, voice, media, sign
|
|
131
|
+
channel = chat.channel_id("#general")
|
|
132
|
+
for message in chat.messages(channel, limit=20)["messages"]:
|
|
133
|
+
print(message["author"]["username"], message["body"])
|
|
134
|
+
chat.send_message(channel, "Hello from Python!")
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`login_with_velora` asks Nookara for a short ticket made for that one instance and
|
|
138
|
+
trades it at `POST /api/v1/auth/velora`. Your Nookara token never leaves Nookara. The
|
|
139
|
+
ticket carries the instance scopes the person allowed (`chat`, `voice`, `media`, `sign`);
|
|
140
|
+
`sign="auto"` signs only when Sign Messages was allowed, `sign=True` raises `ScopeError`
|
|
141
|
+
if not. The instance session lasts 30 days from last use.
|
|
142
|
+
|
|
143
|
+
Discovery helpers: `accounts_mode`, `features`, `build` (version, commit, build_id),
|
|
144
|
+
`read_only`, `instance_info()` (`GET /api/v1/instance/info`, admins also get runtime, stats
|
|
145
|
+
and `update`) and `update_available()`. `Client.server_latest()` returns the official
|
|
146
|
+
build instances compare with.
|
|
147
|
+
|
|
148
|
+
Useful methods: `servers()` / `joined_communities()`, `channels()`, `members()`,
|
|
149
|
+
`send_message()`, `edit_message()`, `delete_message()`, `react()`, `pin()`, `search()`,
|
|
150
|
+
`join()`, `invites()`, `create_invite(expires_in=, max_uses=)`, `delete_invite()`,
|
|
151
|
+
`join_invite(code)`, `invite_info(code)`, `join_requests()`, `accept_join_request(uid)`,
|
|
152
|
+
`create_channel()`, `set_channel_permissions()`, `kick()`, `webhooks()`,
|
|
153
|
+
`create_webhook()`, `bots()`, `create_bot()`, `backups()`, `create_backup()`, `settings()`,
|
|
154
|
+
`identities(ids)`, `set_notify()`. Every route also has a named method
|
|
155
|
+
(`get_api_v1_servers_by_sid`) and `instance.call(...)`. IDs are strings; users look like
|
|
156
|
+
`v:123`, `l:7`, `b:3`.
|
|
157
|
+
|
|
158
|
+
### Your servers as one object
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
from velora_api import Bonfire
|
|
162
|
+
|
|
163
|
+
bonfire = Bonfire(velora, sign="auto")
|
|
164
|
+
bonfire.join_default() # the official server, once per account
|
|
165
|
+
for item in bonfire.communities(): # one entry per saved server
|
|
166
|
+
print(item["instance"], (item["community"] or {}).get("name"), item["error"])
|
|
167
|
+
bonfire.add("https://camp-bonfire.ikelene.net") # saved on the account, follows the person between devices
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
A server that is down, banned, in local-account mode, or where Chat was declined is
|
|
171
|
+
reported in `error` (`unreachable`, `local_accounts`, `chat_scope_missing`, an API code);
|
|
172
|
+
it never stops the others.
|
|
173
|
+
|
|
174
|
+
### Uploads and media import
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
uploaded = chat.upload("picture.png", cid=channel) # needs the media scope
|
|
178
|
+
saved = chat.import_media("https://example.com/cat.png", cid=channel) # the instance fetches an https URL
|
|
179
|
+
chat.send_message(channel, "A picture", attachments=[uploaded, saved])
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
A session without the `media` scope raises `ScopeError` before anything is sent.
|
|
183
|
+
Uploads also need the community's `allow_uploads` and the channel's Attach permission.
|
|
184
|
+
|
|
185
|
+
### Signing and verifying
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
chat.login_with_velora(velora, sign=True) # account key from Nookara, kept in memory
|
|
189
|
+
sent = chat.send_message(channel, "Signed")
|
|
190
|
+
ok, reason = chat.verify_message(sent["message"]) # you check it, not the instance's `verified` flag
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Signing is optional on every velora-mode instance and never required. It needs the
|
|
194
|
+
Sign Messages scope and the `signing` extra. Signatures follow protocol section 8, including
|
|
195
|
+
`url:` entries for link-only GIFs. `verify_message` reads the public key from Nookara and
|
|
196
|
+
checks owner, revocation time and the Ed25519 signature.
|
|
197
|
+
|
|
198
|
+
### Webhooks need no login
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
from velora_api import execute_webhook
|
|
202
|
+
execute_webhook("https://chat.ikelene.net/api/v1/webhooks/ID/TOKEN", "Build finished")
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Bots
|
|
206
|
+
|
|
207
|
+
An **instance bot** is created on one instance (`chat.create_bot("pingbot")` returns the
|
|
208
|
+
token once) and uses a bot token. REST is Discord-style at `/bot/v1` with numeric ids, the
|
|
209
|
+
bot gateway is `/bot/gateway`:
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
bot = Instance("https://chat.ikelene.net", bot_token=os.environ["BONFIRE_BOT_TOKEN"])
|
|
213
|
+
async with bot.bot_gateway() as gateway:
|
|
214
|
+
async for event in gateway: # {"type": "MESSAGE_CREATE", "data": {...}}
|
|
215
|
+
...
|
|
216
|
+
bot.bot_send("NUMERIC_CHANNEL_ID", "pong")
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
A **Nookara developer bot** lives on your Nookara account and can be listed in the bot
|
|
220
|
+
gallery (Discovery > Bots) so others add it to their servers. Each instance makes its own
|
|
221
|
+
bot account for it, but the token is the same. A bot program serves all of them:
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
from velora_api import BotProgram
|
|
225
|
+
|
|
226
|
+
async with BotProgram(os.environ["VELORA_BOT_TOKEN"]) as bot: # GET /bonfire/bots/self/installs
|
|
227
|
+
async for event in bot: # {"instance", "type", "data", "seq"}
|
|
228
|
+
if event["type"] == "MESSAGE_CREATE" and event["data"]["content"] == "!ping":
|
|
229
|
+
await bot.send(event["instance"], event["data"]["channel_id"], "pong")
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
It opens one gateway per install, re-reads the install list every 5 minutes, and keeps
|
|
233
|
+
going when one instance fails. Gallery owner calls: `list_gallery()`, `save_gallery_listing()`,
|
|
234
|
+
`submit_to_gallery()`, `gallery_status()`, `withdraw_from_gallery()`, `gallery_ticket()` and
|
|
235
|
+
`Instance.add_gallery_bot(ticket)`.
|
|
236
|
+
|
|
237
|
+
### User gateway
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
chat.login_with_velora(velora)
|
|
241
|
+
async with chat.gateway() as gateway:
|
|
242
|
+
async for event in gateway:
|
|
243
|
+
if event["type"] == "chat_message":
|
|
244
|
+
print(event["message"]["body"])
|
|
245
|
+
# or: @gateway.on("chat_message") ... then: await gateway.run()
|
|
246
|
+
await_ack = await gateway.send_message(channel, "hi") # waits for chat_ack
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
It reconnects with capped backoff and jitter (1s to 30s), pings every 25 s, and stops on
|
|
250
|
+
close codes 4001 and 4003. There is no replay: after `gateway_ready` follows a drop,
|
|
251
|
+
refetch history over REST and dedupe by id.
|
|
252
|
+
|
|
253
|
+
### Other Nookara-side Bonfire helpers
|
|
254
|
+
|
|
255
|
+
`Client`: `instance_token(url)`, `signing_key()`, `bonfire_instances()`,
|
|
256
|
+
`set_bonfire_instances(urls)`, `sync_bonfire_instances(urls)`, `bonfire_default_instance()`,
|
|
257
|
+
`bonfire_default_joined()`, `bonfire_instance_prefs()`, `bonfire_directory(q)`,
|
|
258
|
+
`bonfire_profiles(ids)`, `bonfire_key(kid)`, `bonfire_jwks()`, `bonfire_scopes()`,
|
|
259
|
+
`bonfire_import_platforms()` / `start_bonfire_import()` (Discord and Fluxer bundles),
|
|
260
|
+
and hosting: `hosting_plans()`, `hosting_servers()`,
|
|
261
|
+
`create_hosting_server(slug, name, plan, weeks=)`, `buy_hosting_weeks(slug, weeks)`,
|
|
262
|
+
`backup_hosting_server(slug)`, `hosting_backups()`, `upload_hosting_backup(...)`,
|
|
263
|
+
`delete_hosting_server(slug)`. Plus is prepaid in weeks and never renews by itself; it is
|
|
264
|
+
free for Nookara Pro. Examples are in `examples/bonfire_*.py`.
|
|
265
|
+
|
|
266
|
+
## Nookara REST methods
|
|
267
|
+
|
|
268
|
+
Nookara-hosted chat servers (`/chat/servers`, `/chat/channels` and so on) are
|
|
269
|
+
legacy and read-only now (writes answer `server_read_only` with `moved_to`). Direct
|
|
270
|
+
messages, groups, friends, storage, credits and presence are unchanged.
|
|
271
|
+
|
|
272
|
+
```python
|
|
273
|
+
velora.send_dm("someone", "Hello from Python!")
|
|
274
|
+
velora.set_activity("My game", state="In the menu", ttl=600)
|
|
275
|
+
velora.clear_activity()
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
The old `servers()`, `server()`, `messages()` and `send_message()` helpers on `Client`
|
|
279
|
+
still call the legacy routes (they now emit a `DeprecationWarning`); use `Instance` for
|
|
280
|
+
new work.
|
|
281
|
+
|
|
282
|
+
Group chats have reactions, edits and pins, and DMs have pins (0.6.3):
|
|
283
|
+
|
|
284
|
+
```python
|
|
285
|
+
velora.patch_groups_by_room_messages_by_id(room="grp_x", id="a1b2c3", json={"message": "fixed"}) # your own message
|
|
286
|
+
velora.delete_groups_by_room_messages_by_id(room="grp_x", id="a1b2c3") # your own message
|
|
287
|
+
velora.put_groups_by_room_messages_by_id_reactions_by_emoji(room="grp_x", id="a1b2c3", emoji="\N{THUMBS UP SIGN}")
|
|
288
|
+
velora.delete_groups_by_room_messages_by_id_reactions_by_emoji(room="grp_x", id="a1b2c3", emoji="\N{THUMBS UP SIGN}")
|
|
289
|
+
velora.put_groups_by_room_messages_by_id_pin(room="grp_x", id="a1b2c3") # any member, 50 per group
|
|
290
|
+
velora.get_groups_by_room_pins(room="grp_x")["messages"]
|
|
291
|
+
velora.put_im_by_id_pin(id=123) # either person, 50 per conversation
|
|
292
|
+
velora.get_im_with_by_username_pins(username="someone")["messages"]
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
The realtime client gets `room_message_edited`, `room_message_reactions`, `room_message_deleted`, `room_pins_update`,
|
|
296
|
+
`im_deleted` and `im_pins_update`.
|
|
297
|
+
|
|
298
|
+
`send_dm(username, message)` requires Send Instant Messages (128). Storage uses Read (16) /
|
|
299
|
+
Write (32); its values are strings. Use `json.dumps()` / `json.loads()` for structured data.
|
|
300
|
+
|
|
301
|
+
Every operation in the downloadable catalogues (Nookara on `Client`, instances on `Instance`)
|
|
302
|
+
has a named method. For example:
|
|
303
|
+
|
|
304
|
+
```python
|
|
305
|
+
chat.post_api_v1_servers_by_sid_roles(
|
|
306
|
+
sid="YOUR_SERVER_SID", json={"name": "Member", "permissions": 257}
|
|
307
|
+
)
|
|
308
|
+
chat.call("get_api_v1_servers_by_sid", sid="YOUR_SERVER_SID")
|
|
309
|
+
velora.call("get_bonfire_hosting_plans")
|
|
310
|
+
velora.request("GET", "/bonfire/instances")
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Use `query={...}` for queries, `json={...}` for JSON, or `data={...}, files={...}` for
|
|
314
|
+
multipart. Path parameters are URL encoded; optional parameters may be omitted. The API
|
|
315
|
+
checks token scopes, server/channel roles, rank and privacy separately. File values can
|
|
316
|
+
also be `(filename, bytes_or_file_object, mime_type)`. This client buffers multipart data
|
|
317
|
+
with a 64 MiB limit; server limits can be smaller.
|
|
318
|
+
|
|
319
|
+
## Errors
|
|
320
|
+
|
|
321
|
+
```python
|
|
322
|
+
from velora_api import APIError, ScopeError, TransportError
|
|
323
|
+
|
|
324
|
+
try:
|
|
325
|
+
chat.upload("picture.png")
|
|
326
|
+
except ScopeError as exc:
|
|
327
|
+
print("Log in again and keep this permission ticked:", exc.label or exc.error)
|
|
328
|
+
except APIError as exc:
|
|
329
|
+
print(exc.status, exc.error, exc.retry_after, exc.moved_to)
|
|
330
|
+
except TransportError:
|
|
331
|
+
print("Network error. Check whether a write completed before retrying.")
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
The library does not retry requests automatically. Handle 429 using Retry-After or
|
|
335
|
+
`retry_after` when present, with backoff. Error payloads are available as `exc.payload`.
|
|
336
|
+
Some endpoints return empty 204, text or binary responses; use `raw=True` for bytes.
|
|
337
|
+
Redirects are rejected so credentials are never forwarded.
|
|
338
|
+
|
|
339
|
+
## Realtime user client (Nookara)
|
|
340
|
+
|
|
341
|
+
```python
|
|
342
|
+
import asyncio
|
|
343
|
+
import os
|
|
344
|
+
from velora_api import RealtimeClient
|
|
345
|
+
|
|
346
|
+
async def main():
|
|
347
|
+
async with RealtimeClient(os.environ["VELORA_TOKEN"]) as socket:
|
|
348
|
+
await socket.set_activity("My Python app", state="Connected")
|
|
349
|
+
async for event in socket:
|
|
350
|
+
if event.get("type") in ("im_received", "presence_update"):
|
|
351
|
+
print(event)
|
|
352
|
+
|
|
353
|
+
asyncio.run(main())
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
This is Nookara's own socket: presence, direct messages, groups, calls. Entering the context
|
|
357
|
+
waits for `auth_success`, checks `tos_required`, starts the reader and sends `chat_hello`
|
|
358
|
+
when the Chat scope is granted. Its `chat_*` frames only concern legacy Nookara-hosted
|
|
359
|
+
servers; Bonfire chat uses the instance gateway above. `rpc()` is only for frames with nonce
|
|
360
|
+
replies. On disconnect, reconnect, authenticate, refetch REST state and rebuild presence.
|
|
361
|
+
There is no durable websocket replay.
|
|
362
|
+
|
|
363
|
+
This package transports voice signaling but does not implement WebRTC, audio
|
|
364
|
+
capture/playback or a desktop UI. See the voice guide for a WebRTC implementation,
|
|
365
|
+
including two peer connections and subscription snapshots.
|
|
366
|
+
|
|
367
|
+
## Legacy Nookara bots and webhooks
|
|
368
|
+
|
|
369
|
+
These Nookara-hosted routes are read-only now; use the instance bots and webhooks above.
|
|
370
|
+
|
|
371
|
+
```python
|
|
372
|
+
bot = Client(os.environ["VELORA_BOT_TOKEN"], auth_scheme="Bot")
|
|
373
|
+
me = bot.get_chat_api_v1_users_me()
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
## Game servers (Bonfire Link, Minecraft)
|
|
377
|
+
|
|
378
|
+
On a Bonfire instance the game plugin connects out to the instance
|
|
379
|
+
(`bonfire server https://chat.example` in the game console); an admin picks it from the
|
|
380
|
+
connected list:
|
|
381
|
+
|
|
382
|
+
```python
|
|
383
|
+
for server in chat.game_connected()["instances"]: # waiting to be linked (Manage server)
|
|
384
|
+
print(server["iid"], server["pairing_tag"], server["motd"])
|
|
385
|
+
chat.game_link(iid, cid=GAME_CHANNEL_CID)
|
|
386
|
+
print(chat.game_status(GAME_CHANNEL_CID))
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
`Client.game_*` still exist for the legacy Nookara routes. Console commands may require the
|
|
390
|
+
website or the Bonfire app (`console_needs_bonfire`).
|
|
391
|
+
|
|
392
|
+
## Bot payments (0.5.0)
|
|
393
|
+
|
|
394
|
+
A bot can ask a Nookara user for credits. The bot authenticates with its **bot token**; the buyer
|
|
395
|
+
opens the returned `pay_url`, signs in with their normal login (no scopes, no app authorization)
|
|
396
|
+
and presses Pay. The credits go to the bot's owner. There is no webhook, so you poll.
|
|
397
|
+
|
|
398
|
+
```python
|
|
399
|
+
from velora_api import BotClient, is_paid, OrderConflict, BuyerError, PaymentError
|
|
400
|
+
|
|
401
|
+
bot = BotClient(os.environ["BOT_TOKEN"])
|
|
402
|
+
request = bot.create_payment_request("alice", 20, "ord-1001", description="Premium pack",
|
|
403
|
+
return_url="https://shop.example/done", expires_in=900)
|
|
404
|
+
print(request["pay_url"]) # send the buyer here, do not log it publicly
|
|
405
|
+
final = bot.wait_for_payment(request["id"], timeout=900, poll=3) # PaymentTimeout when still pending
|
|
406
|
+
if is_paid(final) and final["amount"] == 20: # fulfil ONLY when paid; check amount and buyer id
|
|
407
|
+
...
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
`get_payment_request(id)`, `find_payment_request(order_id)` (None if unknown),
|
|
411
|
+
`list_payment_requests(status=None, limit=None)` and `cancel_payment_request(id)` complete the set.
|
|
412
|
+
Repeating `create_payment_request` with the same order id, buyer and amount returns the existing request
|
|
413
|
+
(`idempotent_replay` is true); a different buyer or amount raises `OrderConflict`. Errors are
|
|
414
|
+
`PaymentError` subclasses (still `APIError`): `InvalidBotToken`, `MerchantUnavailable`,
|
|
415
|
+
`InvalidPaymentRequest`, `BuyerError`, `OrderConflict`, `PaymentInProgress`, `NotCancellable`,
|
|
416
|
+
`PaymentNotFound` and `PaymentRateLimited`; `error.error` keeps the server code. Run this on a server:
|
|
417
|
+
**never put a bot token in a Scratch/PenguinMod project, web page or app** (the PenguinMod extension has no
|
|
418
|
+
payment blocks for that reason). See `examples/bot_payments.py` and the Bot payments section of the guide.
|
|
419
|
+
|
|
420
|
+
Bot presence: a bot is online in a community while it holds an authenticated `/bot/gateway` connection.
|
|
421
|
+
Member objects from `Instance.members(...)` carry `presence` (`"online"` or `"offline"` for bots, `null` for
|
|
422
|
+
people) and the instance websocket (`InstanceGateway`) delivers `chat_presence` events; the library passes these through unchanged.
|
|
423
|
+
`Client.bot_installs()` includes staff test installs with `test: true` (not counted as real installs; skip them).
|
|
424
|
+
|
|
425
|
+
## Notifications (0.5.7)
|
|
426
|
+
|
|
427
|
+
Friend requests, credits, store sales, badges, membership, hosting and messages from the Nookara team are
|
|
428
|
+
notifications (the bell on the site and in Ember). System DMs are gone: those messages are notifications now.
|
|
429
|
+
|
|
430
|
+
```python
|
|
431
|
+
page = velora.notifications(unread=True, limit=20) # {"notifications": [...], "next_cursor": ..., "unread": n}
|
|
432
|
+
for n in page["notifications"]:
|
|
433
|
+
print(n["category"], n["title"], n["body"]) # plain text, **bold** is the only markup
|
|
434
|
+
velora.mark_notifications_read([n["id"] for n in page["notifications"]]) # or mark_notifications_read(all=True)
|
|
435
|
+
print(velora.unread_notifications())
|
|
436
|
+
for category in velora.notification_settings(): # locked ones (system, moderation) are always on
|
|
437
|
+
print(category["key"], category["list"], category["email"], category["locked"])
|
|
438
|
+
velora.set_notification_settings({"friends": {"email": False}})
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
`mark_notifications_unread(ids)` and `delete_notification(id)` complete the set; the generated methods are
|
|
442
|
+
`get_account_notifications`, `get_account_notifications_unread_count`, `post_account_notifications_read`,
|
|
443
|
+
`post_account_notifications_unread`, `delete_account_notifications_by_id`, `get_account_notification_settings`
|
|
444
|
+
and `patch_account_notification_settings`. Live changes arrive on the user websocket:
|
|
445
|
+
|
|
446
|
+
```python
|
|
447
|
+
async with RealtimeClient(os.environ["VELORA_TOKEN"]) as socket:
|
|
448
|
+
async for event in socket.notification_events(): # notification_new / notifications_updated
|
|
449
|
+
print(event["type"], event["unread"])
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
`notification_new` carries `notification` (same shape as the list) and `unread`; `notifications_updated` carries
|
|
453
|
+
`unread` and one of `read` (ids or `"all"`), `unread_ids` or `deleted`. The older `/notifications` routes still
|
|
454
|
+
exist for the Scratch extension. Staff can send with `post_admin_notifications_send`.
|
|
455
|
+
|
|
456
|
+
## Bot aliases (0.5.8)
|
|
457
|
+
|
|
458
|
+
A bot can post a message under another name and avatar. The message still belongs to the bot (mentions,
|
|
459
|
+
moderation and reports use the real bot); clients show the alias with a mask icon and the tooltip
|
|
460
|
+
"Posted by BOT as ALIAS". Only bot tokens may set an alias; a user or session token gets `403 alias_bots_only` (`APIError`), also for `alias=None` and on the gateway `chat_send`.
|
|
461
|
+
|
|
462
|
+
```python
|
|
463
|
+
from velora_api import Instance, make_alias
|
|
464
|
+
|
|
465
|
+
bot = Instance("https://chat.ikelene.net", bot_token=os.environ["BOT_TOKEN"])
|
|
466
|
+
sent = bot.bot_send("CHANNEL_ID", "Welcome in!", alias={"name": "Narrator", "avatar_url": "https://example.com/n.png"})
|
|
467
|
+
print(sent["alias"]) # {"name": "Narrator", "avatar_url": "https://example.com/n.png"}
|
|
468
|
+
bot.bot_edit("CHANNEL_ID", sent["id"], alias=make_alias("Storyteller")) # change it
|
|
469
|
+
bot.bot_edit("CHANNEL_ID", sent["id"], alias=None) # clear it
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
`name` is 1-32 characters with no line breaks and `avatar_url` is optional (`None` or `""` = no avatar), `https://` only, at most 500
|
|
473
|
+
characters, no `user:pass@`, no whitespace; leaving `alias` out of `bot_edit` keeps the current one; `make_alias` checks both before the request (`ValueError`). `alias=` is also accepted by
|
|
474
|
+
`BotProgram.send(...)` (through its fields), `Instance.send_message`, `Instance.edit_message` and
|
|
475
|
+
`InstanceGateway.send_message`. Messages are plain dicts in this library, so read `message["alias"]`, which is
|
|
476
|
+
`None` or `{"name", "avatar_url", "unmarked"}`; `message["author"]` is always the real bot.
|
|
477
|
+
|
|
478
|
+
Unmarked aliases: when an Ember server's owner turns on Server settings > Danger zone > Advanced > "Allow unmarked aliases"
|
|
479
|
+
(`advanced.unmarked_aliases`, owner only) and the bot has the "Use unmarked aliases" role permission (4096) or Administrator,
|
|
480
|
+
its aliases come back with `"unmarked": True` and, when the name matched exactly one member of that server, `"member_id"`
|
|
481
|
+
(a principal on the client API, a numeric id on the bot API). Clients then show the alias like a normal member message. Nothing
|
|
482
|
+
changes in this library: the fields are just more dict keys, and `message["author"]` is still the real bot.
|
|
483
|
+
|
|
484
|
+
## Complete reference
|
|
485
|
+
|
|
486
|
+
https://nookara.cc/guides/velora-api
|
|
487
|
+
|
|
488
|
+
The source catalogue records the live API on 2026-10-19, including staff routes. Not every
|
|
489
|
+
input has a strict JSON Schema and not every privileged operation is tested live. The
|
|
490
|
+
server remains authoritative. Internal bridge/payment integration credentials and
|
|
491
|
+
deployment callbacks are not public client APIs.
|
|
492
|
+
|
|
493
|
+
## Changes in 0.6.1
|
|
494
|
+
|
|
495
|
+
- Fix `bad_signature` when sending signed messages after the move to Nookara: `enable_signing()` now signs with the issuer the instance announces (`/.well-known/bonfire` `velora.issuer`, falling back to `https://velora.ikelene.net`) instead of the base URL you logged in through. `Instance.issuer()` returns it, and `verify_message()` accepts the announced issuer first, then the other known Nookara origins.
|
|
496
|
+
|
|
497
|
+
## Changes in 0.6.0
|
|
498
|
+
|
|
499
|
+
- Velora is now Nookara: the default base URL is `https://nookara.cc` (and `wss://nookara.cc/` for realtime). `velora.ikelene.net` keeps working for now; pass `base_url=` / `url=` to keep using it. The import name `velora_api` is unchanged, and a thin `nookara` package re-exports it (`import nookara`).
|
|
500
|
+
|
|
501
|
+
## Changes in 0.5.10
|
|
502
|
+
|
|
503
|
+
- DM files no longer go to catbox.moe. `upload_dm_file(path, destination="temp" | "account")` uploads to Nookara's
|
|
504
|
+
temporary storage (`POST /chat/media/temp`, links at `https://nookara.cc/t/<id>/<name>`, the expiry resets
|
|
505
|
+
on every load) or to your account storage (`POST /chat/media/account`). `upload_destinations()` /
|
|
506
|
+
`get_chat_media_destinations()` says what's available and how much account space is left.
|
|
507
|
+
`post_chat_media_external()` still works and now uploads to temporary storage too.
|
|
508
|
+
|
|
509
|
+
## Changes in 0.5.9
|
|
510
|
+
|
|
511
|
+
- Realtime: Nookara sends `user_update {user, changed, previous_username?, v}` on the user socket whenever someone you can see
|
|
512
|
+
changes their profile (picture, banner, colour, pronouns, bio, website, country, username). It arrives through
|
|
513
|
+
`async for event in socket` like any other frame; patch cached copies by `user["id"]`. No API change.
|
|
514
|
+
|
|
515
|
+
## Changes in 0.5.8
|
|
516
|
+
|
|
517
|
+
- Bot aliases: `alias=` on `Instance.bot_send`, `Instance.bot_edit` (`alias=None` clears), `Instance.send_message`,
|
|
518
|
+
`Instance.edit_message` and `InstanceGateway.send_message`, plus `make_alias(name, avatar_url=None)` which checks
|
|
519
|
+
the limits. `bot_edit` now takes `content` as optional so an alias can be changed alone. Nothing else changed.
|
|
520
|
+
|
|
521
|
+
## Changes in 0.5.7
|
|
522
|
+
|
|
523
|
+
- Notifications: new generated methods for `/account/notifications` (list, unread count, read, unread, delete),
|
|
524
|
+
`/account/notification-settings` (GET, PATCH) and the staff `POST /admin/notifications/send`, helpers
|
|
525
|
+
`notifications()`, `unread_notifications()`, `mark_notifications_read()`, `mark_notifications_unread()`,
|
|
526
|
+
`delete_notification()`, `notification_settings()`, `set_notification_settings()`, and
|
|
527
|
+
`RealtimeClient.notification_events()` with `NOTIFICATION_EVENT_TYPES` (`notification_new`, `notifications_updated`).
|
|
528
|
+
- System DMs (`from_system` messages) are no longer created; those messages are notifications now.
|
|
529
|
+
|
|
530
|
+
## Changes in 0.5.1
|
|
531
|
+
|
|
532
|
+
- New `get_chat_media_limits()`: the real upload limits (`server_mb` for files sent in a conversation, `saved_mb` for files kept
|
|
533
|
+
on your Nookara account). `get_chat_unfurl()` can now return `payment` cards for Nookara payment links. Nothing else changed.
|
|
534
|
+
|
|
535
|
+
## Changes in 0.5.0
|
|
536
|
+
|
|
537
|
+
- New `BotClient` (bot token) with bot payment helpers: `create_payment_request`,
|
|
538
|
+
`get_payment_request`, `find_payment_request`, `list_payment_requests`, `cancel_payment_request`,
|
|
539
|
+
`wait_for_payment`, `is_paid`, plus the `PaymentError` family. The four `/bot/payment-requests`
|
|
540
|
+
routes are also generated methods (`post_bot_payment_requests`, ...).
|
|
541
|
+
- Docs for bot presence and staff test installs (`test: true`). No behaviour changes elsewhere.
|
|
542
|
+
|
|
543
|
+
## Changes in 0.4.0
|
|
544
|
+
|
|
545
|
+
- Scopes are only requested: `login(scopes=...)` sends them as an optional request, the new
|
|
546
|
+
`scopes` module and `Client.granted_scopes()/has_scope()/require_scope()` report what the
|
|
547
|
+
person allowed, and calls without a scope raise `ScopeError` (an `APIError` subclass).
|
|
548
|
+
- Instance scopes (`chat`, `voice`, `media`, `sign`) from the ticket: `Instance.scopes`,
|
|
549
|
+
`has_scope()`, `sign="auto"`, early `ScopeError` for uploads.
|
|
550
|
+
- New: `Bonfire` (your saved servers), `BotProgram` (a bot on every instance it is
|
|
551
|
+
installed on), `verify_message`, invites, join requests, media import, channel and role
|
|
552
|
+
helpers, game/bridge/admin helpers, instance info and build, Nookara helpers for profiles,
|
|
553
|
+
keys, JWKS, scopes, import jobs, default server and push preferences.
|
|
554
|
+
- 458 Nookara and 169 instance operations as named methods.
|
|
555
|
+
- Legacy `Client.servers()/server()/messages()/send_message()` warn that Nookara-hosted chat is read-only.
|
|
556
|
+
|
|
557
|
+
## Encrypted direct messages
|
|
558
|
+
|
|
559
|
+
DMs are end-to-end encrypted, so `Client.send_dm` no longer sends plain text. A client makes its own keys and asks the person to approve it in Ember:
|
|
560
|
+
|
|
561
|
+
```python
|
|
562
|
+
from velora_api import EncryptedDMs
|
|
563
|
+
|
|
564
|
+
dms = EncryptedDMs(client, key_file="nookara-dms.json") # pip install "nookara[signing]"
|
|
565
|
+
if not dms.linked():
|
|
566
|
+
code = dms.request_link()
|
|
567
|
+
print("In Ember: Settings > Privacy > Approve a device, and enter", code)
|
|
568
|
+
dms.wait_for_approval(code)
|
|
569
|
+
dms.send("alice", "Hello!")
|
|
570
|
+
for m in dms.messages("alice"):
|
|
571
|
+
print(m["from"], dms.open(m))
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
Before linking, the Direct Messages permission only gets locked messages and who/when. `key_file` holds your private keys: protect it like a password. People can unlink your client any time. Someone aged 13-15 can't give this permission to other people's Apps.
|