manikineko 0.2.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.
- manikineko-0.2.0/PKG-INFO +263 -0
- manikineko-0.2.0/README.md +244 -0
- manikineko-0.2.0/manikineko/__init__.py +89 -0
- manikineko-0.2.0/manikineko/admin.py +389 -0
- manikineko-0.2.0/manikineko/client.py +1194 -0
- manikineko-0.2.0/manikineko/dialer.py +177 -0
- manikineko-0.2.0/manikineko/errors.py +41 -0
- manikineko-0.2.0/manikineko/gateway.py +215 -0
- manikineko-0.2.0/manikineko/http.py +168 -0
- manikineko-0.2.0/manikineko/oauth.py +179 -0
- manikineko-0.2.0/manikineko/types.py +955 -0
- manikineko-0.2.0/manikineko.egg-info/PKG-INFO +263 -0
- manikineko-0.2.0/manikineko.egg-info/SOURCES.txt +16 -0
- manikineko-0.2.0/manikineko.egg-info/dependency_links.txt +1 -0
- manikineko-0.2.0/manikineko.egg-info/requires.txt +6 -0
- manikineko-0.2.0/manikineko.egg-info/top_level.txt +1 -0
- manikineko-0.2.0/pyproject.toml +32 -0
- manikineko-0.2.0/setup.cfg +4 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: manikineko
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Official SDK for Manikineko — Discord-compatible REST, realtime gateway, OAuth2 and app-controlled gateway servers.
|
|
5
|
+
Author: Manikineko
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://manikineko.nl
|
|
8
|
+
Keywords: manikineko,chat,bot,discord-compatible,oauth2
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Communications :: Chat
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Provides-Extra: gateway
|
|
16
|
+
Requires-Dist: websockets>=12; extra == "gateway"
|
|
17
|
+
Provides-Extra: all
|
|
18
|
+
Requires-Dist: websockets>=12; extra == "all"
|
|
19
|
+
|
|
20
|
+
# manikineko (Python SDK)
|
|
21
|
+
|
|
22
|
+
Official SDK for the [Manikineko](https://manikineko.nl) platform.
|
|
23
|
+
Python >= 3.9. REST + OAuth2 helpers are stdlib-only; the realtime gateway
|
|
24
|
+
needs the `websockets` package.
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install manikineko # REST + OAuth only
|
|
28
|
+
pip install "manikineko[gateway]" # + realtime gateway (websockets)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Bot quickstart
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from manikineko import Client
|
|
35
|
+
|
|
36
|
+
client = Client(
|
|
37
|
+
base_url="https://api.manikineko.nl", # your instance's API host
|
|
38
|
+
token="BOT_TOKEN", # from the developer portal
|
|
39
|
+
token_type="bot",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
@client.event
|
|
43
|
+
async def on_ready(ready):
|
|
44
|
+
print(f"Logged in as {ready['user']['username']}")
|
|
45
|
+
|
|
46
|
+
@client.event
|
|
47
|
+
async def on_message_create(msg):
|
|
48
|
+
if msg.get("author", {}).get("bot"):
|
|
49
|
+
return
|
|
50
|
+
if msg.get("content") == "!ping":
|
|
51
|
+
client.say(msg["channel_id"], "pong")
|
|
52
|
+
|
|
53
|
+
client.run() # connects the gateway, blocks forever
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The SDK speaks the Discord-compatible API (`/api/v10`): snowflake IDs,
|
|
57
|
+
integer channel types, Discord-shaped events — so discord.py-style code
|
|
58
|
+
ports over easily. REST methods are synchronous; event handlers may be
|
|
59
|
+
sync or async.
|
|
60
|
+
|
|
61
|
+
## What's covered
|
|
62
|
+
|
|
63
|
+
- **REST** — users, guilds, channels, messages, reactions, pins, typing,
|
|
64
|
+
members, bans, roles, invites, audit logs, DMs, reports, HTML channels.
|
|
65
|
+
- **Gateway** — `client.run()`, heartbeats, reconnects, `@client.event`,
|
|
66
|
+
`gateway.request_members()`, `client.set_presence()`.
|
|
67
|
+
- **Voice / VC** — join/leave calls in *any* channel type (incl. DMs),
|
|
68
|
+
voice states, mute/deafen/move moderation, LiveKit credentials or mesh
|
|
69
|
+
WebRTC signalling, `VOICE_STATE_UPDATE` events.
|
|
70
|
+
- **PSTN dialer** — `client.dialer()` places real phone calls into voice
|
|
71
|
+
channels when the instance has SIP/VoIP configured.
|
|
72
|
+
- **Custom emotes** — platform + guild emote listing/CRUD, usable-set
|
|
73
|
+
endpoint, `Client.emoji_tag()` helper for `<:name:id>` content.
|
|
74
|
+
- **Entitlements** — marketplace app integrations: who owns your app.
|
|
75
|
+
- **Urgent popups** — fetch/acknowledge forced system-bot notices.
|
|
76
|
+
- **Uploads** — `send_message(channel, files=[...])` or `upload_file()`.
|
|
77
|
+
- **OAuth2** — `OAuth2Client`: authorize URLs, PKCE, code exchange, refresh,
|
|
78
|
+
client_credentials, revocation, `@me`.
|
|
79
|
+
- **App push API** — `app_servers()`, `app_create_channel()`,
|
|
80
|
+
`app_send_message()`, `app_dispatch_event()` for app-controlled gateway
|
|
81
|
+
servers; guild link config via `set_guild_gateway()`.
|
|
82
|
+
- **Lexicons** — `list_lexicons()`, `put_lexicon()`, `delete_lexicon()`.
|
|
83
|
+
- **Webhooks** — `verify_gateway_signature()` verifies
|
|
84
|
+
`X-Manikineko-Signature` headers on app-gateway deliveries.
|
|
85
|
+
- **Admin** — `client.admin` namespace (staff JWT): system bots, VoIP
|
|
86
|
+
trunks/numbers/credits, maintenance purges, urgent popups.
|
|
87
|
+
|
|
88
|
+
## Voice / calls
|
|
89
|
+
|
|
90
|
+
Every channel type can host a call — including DM and group DM channels
|
|
91
|
+
(recipient-only). Voice state changes arrive as `VOICE_STATE_UPDATE`.
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
cfg = client.get_voice_config() # {"provider": ..., "voip": bool}
|
|
95
|
+
|
|
96
|
+
# Join a call (any channel). When LiveKit is the media backend the
|
|
97
|
+
# response carries a "livekit" dict {"url", "token"} for a LiveKit SDK.
|
|
98
|
+
state = client.join_voice_channel(channel_id, self_mute=True)
|
|
99
|
+
|
|
100
|
+
client.update_voice_state(self_mute=False)
|
|
101
|
+
participants = client.get_voice_states(channel_id)
|
|
102
|
+
client.leave_voice_channel()
|
|
103
|
+
|
|
104
|
+
# Moderation (server channels only):
|
|
105
|
+
client.voice_mute_member(channel_id, user_id, True)
|
|
106
|
+
client.voice_disconnect_member(channel_id, user_id)
|
|
107
|
+
client.voice_move_member(other_channel_id, user_id)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### PSTN dialer (SIP)
|
|
111
|
+
|
|
112
|
+
When the instance has telephony configured (`VOIP_ENABLED` + SIP trunks),
|
|
113
|
+
bots and users can dial real phone numbers into a voice channel. Outbound
|
|
114
|
+
calls are metered in call credits (HTTP 402 = top up via the shop).
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
dialer = client.dialer()
|
|
118
|
+
if dialer.available():
|
|
119
|
+
print("credits:", dialer.credits())
|
|
120
|
+
|
|
121
|
+
# auto-joins the voice channel, then dials:
|
|
122
|
+
call = dialer.dial(channel_id, "+15551234567")
|
|
123
|
+
print(call.id, call.status) # ringing
|
|
124
|
+
|
|
125
|
+
call.wait_for_connect() # status == "active"
|
|
126
|
+
# ... the phone party is now in the voice room ...
|
|
127
|
+
call.hangup() # or call.wait_for_end()
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Lower-level primitives are on the client too: `dial_phone()`,
|
|
131
|
+
`get_call()`, `get_channel_calls()`, `hangup_call()`, `get_call_credits()`,
|
|
132
|
+
`get_call_credit_history()`. Inbound calls to an instance DID ring into
|
|
133
|
+
the linked voice channel.
|
|
134
|
+
|
|
135
|
+
## Custom emotes
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
emojis = client.get_guild_emojis(guild_id)
|
|
139
|
+
client.create_guild_emoji(guild_id, "nyaa", "https://cdn…/nyaa.png")
|
|
140
|
+
|
|
141
|
+
# in message content: ":nyaa:" resolves by name, or use a tag by ID:
|
|
142
|
+
client.say(channel_id, f"hello {Client.emoji_tag(emojis[0])}")
|
|
143
|
+
|
|
144
|
+
usable = client.get_usable_emojis() # picker payload
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Server emotes outside their home guild need premium; violations return 403.
|
|
148
|
+
|
|
149
|
+
## Entitlements (marketplace app integrations)
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
# As a user:
|
|
153
|
+
owned = client.get_my_entitlements()
|
|
154
|
+
|
|
155
|
+
# As an app (bot token or client_credentials + applications.gateway):
|
|
156
|
+
all_owners = app_client.app_entitlements()
|
|
157
|
+
check = app_client.app_check_entitlement(user_id) # {"entitled": True}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Urgent popups
|
|
161
|
+
|
|
162
|
+
System bots can push forced popups (`type="urgent"`, or admin urgent
|
|
163
|
+
messages). Users see them on `URGENT_MESSAGE` gateway events and via
|
|
164
|
+
`GET /urgent/pending`:
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
@client.on("URGENT_MESSAGE")
|
|
168
|
+
def on_urgent(u):
|
|
169
|
+
print("URGENT:", u["title"], u["body"])
|
|
170
|
+
|
|
171
|
+
pending = client.get_pending_urgent_messages()
|
|
172
|
+
client.acknowledge_urgent(pending[0]["id"]) # dismiss
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Admin API
|
|
176
|
+
|
|
177
|
+
`client.admin` targets `/api/v1/admin/*` and needs a staff JWT
|
|
178
|
+
(`token_type="bearer"`). IDs on this surface are native UUIDs.
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
admin = client.admin
|
|
182
|
+
|
|
183
|
+
admin.system_bots.send_message(
|
|
184
|
+
bot_id, user_ids=[uid1, uid2],
|
|
185
|
+
content="scheduled maintenance tonight",
|
|
186
|
+
type="urgent", severity="warning", title="Heads up",
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
admin.voip.create_trunk(name="twilio", carrier="twilio-byoc",
|
|
190
|
+
host="…sip.twilio.com", prefixes=["+1"],
|
|
191
|
+
rate_credits=2)
|
|
192
|
+
admin.voip.adjust_credits(user_id=uid, amount=100, note="promo")
|
|
193
|
+
|
|
194
|
+
admin.maintenance.purge_stale_reports()
|
|
195
|
+
admin.urgent.create(title="v2 launched", body="…", scope="global")
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Auth
|
|
199
|
+
|
|
200
|
+
| Token kind | `token_type` | Notes |
|
|
201
|
+
| --- | --- | --- |
|
|
202
|
+
| Bot token | `"bot"` (default) | `Authorization: Bot <token>` |
|
|
203
|
+
| OAuth2 access token | `"bearer"` | Scoped — see `Scope` constants |
|
|
204
|
+
| User JWT | `"bearer"` | First-party session token |
|
|
205
|
+
|
|
206
|
+
## OAuth2 ("Login with Manikineko")
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
from manikineko import OAuth2Client, generate_pkce
|
|
210
|
+
|
|
211
|
+
oauth = OAuth2Client(
|
|
212
|
+
base_url="https://api.manikineko.nl",
|
|
213
|
+
client_id="YOUR_CLIENT_ID",
|
|
214
|
+
client_secret="YOUR_CLIENT_SECRET",
|
|
215
|
+
redirect_uri="https://myapp.example/callback",
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
verifier, challenge = generate_pkce()
|
|
219
|
+
url = oauth.authorize_url(["identify", "email"], state=state, code_challenge=challenge)
|
|
220
|
+
|
|
221
|
+
# In your callback handler:
|
|
222
|
+
tokens = oauth.exchange_code(code, code_verifier=verifier)
|
|
223
|
+
me = oauth.user(tokens["access_token"]) # GET /users/@me
|
|
224
|
+
later = oauth.refresh_token(tokens["refresh_token"])
|
|
225
|
+
oauth.revoke(tokens["access_token"])
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
`client_credentials` tokens (e.g. `scope="applications.gateway"`) call the
|
|
229
|
+
app push API:
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
tok = oauth.client_credentials("applications.gateway")
|
|
233
|
+
app = Client(base_url, token=tok["access_token"], token_type="bearer")
|
|
234
|
+
for server in app.app_servers():
|
|
235
|
+
app.app_send_message(server["id"], channel_id, "hello from my app")
|
|
236
|
+
app.app_dispatch_event(server["id"], "score_update", data={"score": 42})
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Gateway events
|
|
240
|
+
|
|
241
|
+
Dispatch names mirror Discord: `READY`, `MESSAGE_CREATE`, `MESSAGE_UPDATE`,
|
|
242
|
+
`MESSAGE_DELETE`, `CHANNEL_*`, `GUILD_*`, `GUILD_MEMBER_*`, `GUILD_ROLE_*`,
|
|
243
|
+
`GUILD_BAN_*`, `MESSAGE_REACTION_*`, `PRESENCE_UPDATE`, `TYPING_START`,
|
|
244
|
+
`INVITE_*`, `VOICE_STATE_UPDATE`. Manikineko extras: `APP_EVENT`,
|
|
245
|
+
`URGENT_MESSAGE`, `WARNING_RECEIVED`, `USER_SUSPENDED`, `USER_BANNED`.
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
@client.on("*")
|
|
249
|
+
def log_all(payload):
|
|
250
|
+
print(payload["event"], payload["data"])
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Escape hatches
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
client.request("GET", "/channels/123/messages") # compat API
|
|
257
|
+
client.api("GET", "/themes") # native /api/v1
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Errors
|
|
261
|
+
|
|
262
|
+
`APIError` (`.status`, `.code`, `.body`), `OAuthError` (`.error`,
|
|
263
|
+
`.error_description`), `InvalidSessionError` on bad gateway tokens.
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# manikineko (Python SDK)
|
|
2
|
+
|
|
3
|
+
Official SDK for the [Manikineko](https://manikineko.nl) platform.
|
|
4
|
+
Python >= 3.9. REST + OAuth2 helpers are stdlib-only; the realtime gateway
|
|
5
|
+
needs the `websockets` package.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install manikineko # REST + OAuth only
|
|
9
|
+
pip install "manikineko[gateway]" # + realtime gateway (websockets)
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Bot quickstart
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
from manikineko import Client
|
|
16
|
+
|
|
17
|
+
client = Client(
|
|
18
|
+
base_url="https://api.manikineko.nl", # your instance's API host
|
|
19
|
+
token="BOT_TOKEN", # from the developer portal
|
|
20
|
+
token_type="bot",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
@client.event
|
|
24
|
+
async def on_ready(ready):
|
|
25
|
+
print(f"Logged in as {ready['user']['username']}")
|
|
26
|
+
|
|
27
|
+
@client.event
|
|
28
|
+
async def on_message_create(msg):
|
|
29
|
+
if msg.get("author", {}).get("bot"):
|
|
30
|
+
return
|
|
31
|
+
if msg.get("content") == "!ping":
|
|
32
|
+
client.say(msg["channel_id"], "pong")
|
|
33
|
+
|
|
34
|
+
client.run() # connects the gateway, blocks forever
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The SDK speaks the Discord-compatible API (`/api/v10`): snowflake IDs,
|
|
38
|
+
integer channel types, Discord-shaped events — so discord.py-style code
|
|
39
|
+
ports over easily. REST methods are synchronous; event handlers may be
|
|
40
|
+
sync or async.
|
|
41
|
+
|
|
42
|
+
## What's covered
|
|
43
|
+
|
|
44
|
+
- **REST** — users, guilds, channels, messages, reactions, pins, typing,
|
|
45
|
+
members, bans, roles, invites, audit logs, DMs, reports, HTML channels.
|
|
46
|
+
- **Gateway** — `client.run()`, heartbeats, reconnects, `@client.event`,
|
|
47
|
+
`gateway.request_members()`, `client.set_presence()`.
|
|
48
|
+
- **Voice / VC** — join/leave calls in *any* channel type (incl. DMs),
|
|
49
|
+
voice states, mute/deafen/move moderation, LiveKit credentials or mesh
|
|
50
|
+
WebRTC signalling, `VOICE_STATE_UPDATE` events.
|
|
51
|
+
- **PSTN dialer** — `client.dialer()` places real phone calls into voice
|
|
52
|
+
channels when the instance has SIP/VoIP configured.
|
|
53
|
+
- **Custom emotes** — platform + guild emote listing/CRUD, usable-set
|
|
54
|
+
endpoint, `Client.emoji_tag()` helper for `<:name:id>` content.
|
|
55
|
+
- **Entitlements** — marketplace app integrations: who owns your app.
|
|
56
|
+
- **Urgent popups** — fetch/acknowledge forced system-bot notices.
|
|
57
|
+
- **Uploads** — `send_message(channel, files=[...])` or `upload_file()`.
|
|
58
|
+
- **OAuth2** — `OAuth2Client`: authorize URLs, PKCE, code exchange, refresh,
|
|
59
|
+
client_credentials, revocation, `@me`.
|
|
60
|
+
- **App push API** — `app_servers()`, `app_create_channel()`,
|
|
61
|
+
`app_send_message()`, `app_dispatch_event()` for app-controlled gateway
|
|
62
|
+
servers; guild link config via `set_guild_gateway()`.
|
|
63
|
+
- **Lexicons** — `list_lexicons()`, `put_lexicon()`, `delete_lexicon()`.
|
|
64
|
+
- **Webhooks** — `verify_gateway_signature()` verifies
|
|
65
|
+
`X-Manikineko-Signature` headers on app-gateway deliveries.
|
|
66
|
+
- **Admin** — `client.admin` namespace (staff JWT): system bots, VoIP
|
|
67
|
+
trunks/numbers/credits, maintenance purges, urgent popups.
|
|
68
|
+
|
|
69
|
+
## Voice / calls
|
|
70
|
+
|
|
71
|
+
Every channel type can host a call — including DM and group DM channels
|
|
72
|
+
(recipient-only). Voice state changes arrive as `VOICE_STATE_UPDATE`.
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
cfg = client.get_voice_config() # {"provider": ..., "voip": bool}
|
|
76
|
+
|
|
77
|
+
# Join a call (any channel). When LiveKit is the media backend the
|
|
78
|
+
# response carries a "livekit" dict {"url", "token"} for a LiveKit SDK.
|
|
79
|
+
state = client.join_voice_channel(channel_id, self_mute=True)
|
|
80
|
+
|
|
81
|
+
client.update_voice_state(self_mute=False)
|
|
82
|
+
participants = client.get_voice_states(channel_id)
|
|
83
|
+
client.leave_voice_channel()
|
|
84
|
+
|
|
85
|
+
# Moderation (server channels only):
|
|
86
|
+
client.voice_mute_member(channel_id, user_id, True)
|
|
87
|
+
client.voice_disconnect_member(channel_id, user_id)
|
|
88
|
+
client.voice_move_member(other_channel_id, user_id)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### PSTN dialer (SIP)
|
|
92
|
+
|
|
93
|
+
When the instance has telephony configured (`VOIP_ENABLED` + SIP trunks),
|
|
94
|
+
bots and users can dial real phone numbers into a voice channel. Outbound
|
|
95
|
+
calls are metered in call credits (HTTP 402 = top up via the shop).
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
dialer = client.dialer()
|
|
99
|
+
if dialer.available():
|
|
100
|
+
print("credits:", dialer.credits())
|
|
101
|
+
|
|
102
|
+
# auto-joins the voice channel, then dials:
|
|
103
|
+
call = dialer.dial(channel_id, "+15551234567")
|
|
104
|
+
print(call.id, call.status) # ringing
|
|
105
|
+
|
|
106
|
+
call.wait_for_connect() # status == "active"
|
|
107
|
+
# ... the phone party is now in the voice room ...
|
|
108
|
+
call.hangup() # or call.wait_for_end()
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Lower-level primitives are on the client too: `dial_phone()`,
|
|
112
|
+
`get_call()`, `get_channel_calls()`, `hangup_call()`, `get_call_credits()`,
|
|
113
|
+
`get_call_credit_history()`. Inbound calls to an instance DID ring into
|
|
114
|
+
the linked voice channel.
|
|
115
|
+
|
|
116
|
+
## Custom emotes
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
emojis = client.get_guild_emojis(guild_id)
|
|
120
|
+
client.create_guild_emoji(guild_id, "nyaa", "https://cdn…/nyaa.png")
|
|
121
|
+
|
|
122
|
+
# in message content: ":nyaa:" resolves by name, or use a tag by ID:
|
|
123
|
+
client.say(channel_id, f"hello {Client.emoji_tag(emojis[0])}")
|
|
124
|
+
|
|
125
|
+
usable = client.get_usable_emojis() # picker payload
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Server emotes outside their home guild need premium; violations return 403.
|
|
129
|
+
|
|
130
|
+
## Entitlements (marketplace app integrations)
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
# As a user:
|
|
134
|
+
owned = client.get_my_entitlements()
|
|
135
|
+
|
|
136
|
+
# As an app (bot token or client_credentials + applications.gateway):
|
|
137
|
+
all_owners = app_client.app_entitlements()
|
|
138
|
+
check = app_client.app_check_entitlement(user_id) # {"entitled": True}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Urgent popups
|
|
142
|
+
|
|
143
|
+
System bots can push forced popups (`type="urgent"`, or admin urgent
|
|
144
|
+
messages). Users see them on `URGENT_MESSAGE` gateway events and via
|
|
145
|
+
`GET /urgent/pending`:
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
@client.on("URGENT_MESSAGE")
|
|
149
|
+
def on_urgent(u):
|
|
150
|
+
print("URGENT:", u["title"], u["body"])
|
|
151
|
+
|
|
152
|
+
pending = client.get_pending_urgent_messages()
|
|
153
|
+
client.acknowledge_urgent(pending[0]["id"]) # dismiss
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Admin API
|
|
157
|
+
|
|
158
|
+
`client.admin` targets `/api/v1/admin/*` and needs a staff JWT
|
|
159
|
+
(`token_type="bearer"`). IDs on this surface are native UUIDs.
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
admin = client.admin
|
|
163
|
+
|
|
164
|
+
admin.system_bots.send_message(
|
|
165
|
+
bot_id, user_ids=[uid1, uid2],
|
|
166
|
+
content="scheduled maintenance tonight",
|
|
167
|
+
type="urgent", severity="warning", title="Heads up",
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
admin.voip.create_trunk(name="twilio", carrier="twilio-byoc",
|
|
171
|
+
host="…sip.twilio.com", prefixes=["+1"],
|
|
172
|
+
rate_credits=2)
|
|
173
|
+
admin.voip.adjust_credits(user_id=uid, amount=100, note="promo")
|
|
174
|
+
|
|
175
|
+
admin.maintenance.purge_stale_reports()
|
|
176
|
+
admin.urgent.create(title="v2 launched", body="…", scope="global")
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Auth
|
|
180
|
+
|
|
181
|
+
| Token kind | `token_type` | Notes |
|
|
182
|
+
| --- | --- | --- |
|
|
183
|
+
| Bot token | `"bot"` (default) | `Authorization: Bot <token>` |
|
|
184
|
+
| OAuth2 access token | `"bearer"` | Scoped — see `Scope` constants |
|
|
185
|
+
| User JWT | `"bearer"` | First-party session token |
|
|
186
|
+
|
|
187
|
+
## OAuth2 ("Login with Manikineko")
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
from manikineko import OAuth2Client, generate_pkce
|
|
191
|
+
|
|
192
|
+
oauth = OAuth2Client(
|
|
193
|
+
base_url="https://api.manikineko.nl",
|
|
194
|
+
client_id="YOUR_CLIENT_ID",
|
|
195
|
+
client_secret="YOUR_CLIENT_SECRET",
|
|
196
|
+
redirect_uri="https://myapp.example/callback",
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
verifier, challenge = generate_pkce()
|
|
200
|
+
url = oauth.authorize_url(["identify", "email"], state=state, code_challenge=challenge)
|
|
201
|
+
|
|
202
|
+
# In your callback handler:
|
|
203
|
+
tokens = oauth.exchange_code(code, code_verifier=verifier)
|
|
204
|
+
me = oauth.user(tokens["access_token"]) # GET /users/@me
|
|
205
|
+
later = oauth.refresh_token(tokens["refresh_token"])
|
|
206
|
+
oauth.revoke(tokens["access_token"])
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
`client_credentials` tokens (e.g. `scope="applications.gateway"`) call the
|
|
210
|
+
app push API:
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
tok = oauth.client_credentials("applications.gateway")
|
|
214
|
+
app = Client(base_url, token=tok["access_token"], token_type="bearer")
|
|
215
|
+
for server in app.app_servers():
|
|
216
|
+
app.app_send_message(server["id"], channel_id, "hello from my app")
|
|
217
|
+
app.app_dispatch_event(server["id"], "score_update", data={"score": 42})
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Gateway events
|
|
221
|
+
|
|
222
|
+
Dispatch names mirror Discord: `READY`, `MESSAGE_CREATE`, `MESSAGE_UPDATE`,
|
|
223
|
+
`MESSAGE_DELETE`, `CHANNEL_*`, `GUILD_*`, `GUILD_MEMBER_*`, `GUILD_ROLE_*`,
|
|
224
|
+
`GUILD_BAN_*`, `MESSAGE_REACTION_*`, `PRESENCE_UPDATE`, `TYPING_START`,
|
|
225
|
+
`INVITE_*`, `VOICE_STATE_UPDATE`. Manikineko extras: `APP_EVENT`,
|
|
226
|
+
`URGENT_MESSAGE`, `WARNING_RECEIVED`, `USER_SUSPENDED`, `USER_BANNED`.
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
@client.on("*")
|
|
230
|
+
def log_all(payload):
|
|
231
|
+
print(payload["event"], payload["data"])
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Escape hatches
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
client.request("GET", "/channels/123/messages") # compat API
|
|
238
|
+
client.api("GET", "/themes") # native /api/v1
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Errors
|
|
242
|
+
|
|
243
|
+
`APIError` (`.status`, `.code`, `.body`), `OAuthError` (`.error`,
|
|
244
|
+
`.error_description`), `InvalidSessionError` on bad gateway tokens.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Official Python SDK for the Manikineko platform.
|
|
2
|
+
|
|
3
|
+
Quickstart::
|
|
4
|
+
|
|
5
|
+
from manikineko import Client
|
|
6
|
+
|
|
7
|
+
client = Client(
|
|
8
|
+
base_url="https://api.manikineko.nl",
|
|
9
|
+
token="BOT_TOKEN",
|
|
10
|
+
token_type="bot",
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
@client.event
|
|
14
|
+
async def on_message_create(msg):
|
|
15
|
+
if msg.get("author", {}).get("bot"):
|
|
16
|
+
return
|
|
17
|
+
if msg.get("content") == "!ping":
|
|
18
|
+
client.say(msg["channel_id"], "pong")
|
|
19
|
+
|
|
20
|
+
client.run()
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from .admin import AdminAPI
|
|
24
|
+
from .client import Client
|
|
25
|
+
from .dialer import Dialer, PhoneCall
|
|
26
|
+
from .errors import (
|
|
27
|
+
APIError,
|
|
28
|
+
InvalidSessionError,
|
|
29
|
+
ManikinekoError,
|
|
30
|
+
OAuthError,
|
|
31
|
+
)
|
|
32
|
+
from .gateway import Gateway
|
|
33
|
+
from .oauth import OAuth2Client, generate_pkce, verify_gateway_signature
|
|
34
|
+
from .types import (
|
|
35
|
+
ApplicationCommandOptionType,
|
|
36
|
+
ApplicationCommandType,
|
|
37
|
+
AutoModAction,
|
|
38
|
+
AutoModTrigger,
|
|
39
|
+
Capability,
|
|
40
|
+
ChannelType,
|
|
41
|
+
GatewayEvent,
|
|
42
|
+
GatewayOp,
|
|
43
|
+
InteractionCallbackType,
|
|
44
|
+
InteractionContextType,
|
|
45
|
+
InteractionType,
|
|
46
|
+
MessageFlags,
|
|
47
|
+
MessageType,
|
|
48
|
+
PrivilegeStatus,
|
|
49
|
+
RPCCommand,
|
|
50
|
+
RPCStatus,
|
|
51
|
+
Scope,
|
|
52
|
+
UserFlags,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
__version__ = "0.2.0"
|
|
56
|
+
|
|
57
|
+
__all__ = [
|
|
58
|
+
"Client",
|
|
59
|
+
"Gateway",
|
|
60
|
+
"OAuth2Client",
|
|
61
|
+
"Dialer",
|
|
62
|
+
"PhoneCall",
|
|
63
|
+
"AdminAPI",
|
|
64
|
+
"generate_pkce",
|
|
65
|
+
"verify_gateway_signature",
|
|
66
|
+
"ManikinekoError",
|
|
67
|
+
"APIError",
|
|
68
|
+
"OAuthError",
|
|
69
|
+
"InvalidSessionError",
|
|
70
|
+
"ChannelType",
|
|
71
|
+
"ApplicationCommandType",
|
|
72
|
+
"ApplicationCommandOptionType",
|
|
73
|
+
"InteractionType",
|
|
74
|
+
"InteractionCallbackType",
|
|
75
|
+
"InteractionContextType",
|
|
76
|
+
"MessageType",
|
|
77
|
+
"MessageFlags",
|
|
78
|
+
"GatewayOp",
|
|
79
|
+
"GatewayEvent",
|
|
80
|
+
"Capability",
|
|
81
|
+
"PrivilegeStatus",
|
|
82
|
+
"RPCCommand",
|
|
83
|
+
"RPCStatus",
|
|
84
|
+
"AutoModTrigger",
|
|
85
|
+
"AutoModAction",
|
|
86
|
+
"Scope",
|
|
87
|
+
"UserFlags",
|
|
88
|
+
"__version__",
|
|
89
|
+
]
|