imbot-sdk-python 0.1.0__tar.gz → 0.1.2__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.
Files changed (45) hide show
  1. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/CHANGELOG.md +18 -0
  2. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/MANIFEST.in +1 -0
  3. imbot_sdk_python-0.1.2/PKG-INFO +366 -0
  4. imbot_sdk_python-0.1.2/README.md +332 -0
  5. imbot_sdk_python-0.1.0/README.md → imbot_sdk_python-0.1.2/README.zh-CN.md +32 -1
  6. imbot_sdk_python-0.1.2/docs/API.md +170 -0
  7. imbot_sdk_python-0.1.0/docs/API.md → imbot_sdk_python-0.1.2/docs/API.zh-CN.md +2 -0
  8. imbot_sdk_python-0.1.2/docs/PROTOCOL.md +116 -0
  9. imbot_sdk_python-0.1.0/docs/PROTOCOL.md → imbot_sdk_python-0.1.2/docs/PROTOCOL.zh-CN.md +21 -0
  10. imbot_sdk_python-0.1.2/examples/register_bot.py +125 -0
  11. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/imbot_sdk/__init__.py +1 -1
  12. imbot_sdk_python-0.1.2/imbot_sdk_python.egg-info/PKG-INFO +366 -0
  13. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/imbot_sdk_python.egg-info/SOURCES.txt +4 -0
  14. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/pyproject.toml +1 -1
  15. imbot_sdk_python-0.1.0/PKG-INFO +0 -296
  16. imbot_sdk_python-0.1.0/imbot_sdk_python.egg-info/PKG-INFO +0 -296
  17. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/LICENSE +0 -0
  18. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/examples/.env.example +0 -0
  19. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/examples/echo_bot.py +0 -0
  20. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/examples/quickstart.py +0 -0
  21. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/examples/run.sh +0 -0
  22. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/imbot_sdk/client.py +0 -0
  23. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/imbot_sdk/consts.py +0 -0
  24. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/imbot_sdk/data_accessor.py +0 -0
  25. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/imbot_sdk/listeners.py +0 -0
  26. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/imbot_sdk/messages/__init__.py +0 -0
  27. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/imbot_sdk/messages/base.py +0 -0
  28. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/imbot_sdk/messages/contents.py +0 -0
  29. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/imbot_sdk/models.py +0 -0
  30. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/imbot_sdk/pb/__init__.py +0 -0
  31. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/imbot_sdk/pb/appmessages_pb2.py +0 -0
  32. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/imbot_sdk/pb/chatroom_pb2.py +0 -0
  33. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/imbot_sdk/pb/connect_pb2.py +0 -0
  34. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/imbot_sdk/pb/rtcroom_pb2.py +0 -0
  35. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/imbot_sdk/py.typed +0 -0
  36. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/imbot_sdk_python.egg-info/dependency_links.txt +0 -0
  37. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/imbot_sdk_python.egg-info/requires.txt +0 -0
  38. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/imbot_sdk_python.egg-info/top_level.txt +0 -0
  39. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/proto/appmessages.proto +0 -0
  40. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/proto/chatroom.proto +0 -0
  41. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/proto/connect.proto +0 -0
  42. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/proto/gen.sh +0 -0
  43. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/proto/rtcroom.proto +0 -0
  44. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/requirements.txt +0 -0
  45. {imbot_sdk_python-0.1.0 → imbot_sdk_python-0.1.2}/setup.cfg +0 -0
@@ -4,6 +4,22 @@ All notable changes to this project are documented here. The format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/), and the project adheres to
5
5
  [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [0.1.2] - 2026-06-26
8
+
9
+ ### Added
10
+
11
+ - `examples/register_bot.py`: register a bot/user via the Server API and print a
12
+ connection token (standard library only, no hard-coded credentials).
13
+ - Token acquisition documented in the README and protocol docs (Server API
14
+ endpoint, signature auth, response envelope).
15
+
16
+ ## [0.1.1] - 2026-06-25
17
+
18
+ ### Changed
19
+
20
+ - Documentation is now bilingual: English `README.md` / `docs/*.md` with Chinese
21
+ `README.zh-CN.md` / `docs/*.zh-CN.md`, linked via a language switcher.
22
+
7
23
  ## [0.1.0] - 2026-06-25
8
24
 
9
25
  ### Added
@@ -24,4 +40,6 @@ All notable changes to this project are documented here. The format follows
24
40
  text, recall info, merge, thumbnail-packed image, snapshot-packed video) with
25
41
  an `UnknownMessage` fallback.
26
42
 
43
+ [0.1.2]: https://github.com/juggleim/imbot-sdk-python/releases/tag/v0.1.2
44
+ [0.1.1]: https://github.com/juggleim/imbot-sdk-python/releases/tag/v0.1.1
27
45
  [0.1.0]: https://github.com/juggleim/imbot-sdk-python/releases/tag/v0.1.0
@@ -1,4 +1,5 @@
1
1
  include README.md
2
+ include README.zh-CN.md
2
3
  include LICENSE
3
4
  include requirements.txt
4
5
  include CHANGELOG.md
@@ -0,0 +1,366 @@
1
+ Metadata-Version: 2.4
2
+ Name: imbot-sdk-python
3
+ Version: 0.1.2
4
+ Summary: JuggleIM Python Bot SDK — WebSocket long-connection IM client for bots and server-side agents
5
+ Author-email: Tyler <tyler@juggle.im>
6
+ Maintainer-email: Tyler <tyler@juggle.im>
7
+ License-Expression: Apache-2.0
8
+ Project-URL: Homepage, https://github.com/juggleim/imbot-sdk-python
9
+ Project-URL: Repository, https://github.com/juggleim/imbot-sdk-python
10
+ Project-URL: Issues, https://github.com/juggleim/imbot-sdk-python/issues
11
+ Project-URL: Documentation, https://github.com/juggleim/imbot-sdk-python#readme
12
+ Keywords: juggleim,im,instant-messaging,chat,bot,websocket,sdk
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Communications :: Chat
24
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
+ Requires-Python: >=3.8
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: protobuf>=5.0
29
+ Requires-Dist: websocket-client>=1.6
30
+ Provides-Extra: dev
31
+ Requires-Dist: build; extra == "dev"
32
+ Requires-Dist: twine; extra == "dev"
33
+ Dynamic: license-file
34
+
35
+ # imbot-sdk-python
36
+
37
+ **English** | [简体中文](README.zh-CN.md)
38
+
39
+ [![PyPI version](https://img.shields.io/pypi/v/imbot-sdk-python.svg)](https://pypi.org/project/imbot-sdk-python/)
40
+ [![Python versions](https://img.shields.io/pypi/pyversions/imbot-sdk-python.svg)](https://pypi.org/project/imbot-sdk-python/)
41
+ [![License](https://img.shields.io/pypi/l/imbot-sdk-python.svg)](https://github.com/juggleim/imbot-sdk-python/blob/main/LICENSE)
42
+
43
+ `imbot-sdk-python` is the JuggleIM Python SDK. It keeps a long-lived WebSocket
44
+ connection to the IM service, making it a good fit for bots, server-side message
45
+ agents, or any Python program that needs to actively send and receive messages.
46
+ It covers connection management, messaging, conversation queries, history,
47
+ chatrooms, user status, RTC rooms and file credential queries.
48
+
49
+ - Source: <https://github.com/juggleim/imbot-sdk-python>
50
+ - Distribution name: `imbot-sdk-python`; import name: `import imbot_sdk`
51
+
52
+ ## Installation
53
+
54
+ ```bash
55
+ pip install imbot-sdk-python
56
+ ```
57
+
58
+ Dependencies (`protobuf>=5.0`, `websocket-client>=1.6`) are installed
59
+ automatically. Python 3.8+.
60
+
61
+ Install from source:
62
+
63
+ ```bash
64
+ git clone https://github.com/juggleim/imbot-sdk-python.git
65
+ cd imbot-sdk-python
66
+ pip install .
67
+ ```
68
+
69
+ ## Test environment
70
+
71
+ | Item | Value |
72
+ | --- | --- |
73
+ | Server API | `http://127.0.0.1` |
74
+ | WebSocket | `wss://127.0.0.1` |
75
+ | AppKey | `AppKey` |
76
+
77
+ > A user token is issued by your JuggleIM app server / console using your AppKey
78
+ > + AppSecret via the Server API. The SDK itself only establishes the
79
+ > connection and sends/receives messages.
80
+
81
+ ## Obtaining a token
82
+
83
+ `connect(token)` needs a per-user token. Tokens are minted by your app server
84
+ through the JuggleIM Server API using your AppKey + AppSecret. Registering a bot
85
+ (or a user) returns a token:
86
+
87
+ - Endpoint: `POST {ServerAPI}/bots/register` (or `/users/register`)
88
+ - Auth headers: `appkey`, `nonce` (random 0-9999), `timestamp` (epoch ms),
89
+ `signature = lowercase_hex( SHA1(AppSecret + nonce + timestamp) )`,
90
+ `Content-Type: application/json`
91
+ - Response: `{"code":0,"msg":"success","data":{"user_id":"...","token":"..."}}`
92
+
93
+ A ready-to-use helper (standard library only, no hard-coded credentials) is
94
+ [`examples/register_bot.py`](examples/register_bot.py):
95
+
96
+ ```bash
97
+ export IMBOT_API_URL="https://api.juggleim.com/apigateway"
98
+ export IMBOT_APPKEY="<your-appkey>"
99
+ export IMBOT_APPSECRET="<your-appsecret>"
100
+
101
+ # register a bot and print its token
102
+ python examples/register_bot.py --bot-id my-bot --nickname "My Bot"
103
+
104
+ # or register a normal user
105
+ python examples/register_bot.py --user --user-id u1 --nickname "User 1"
106
+ ```
107
+
108
+ > Keep your AppSecret on the server side — never ship it inside client apps.
109
+ > If the Server API host presents an expired/invalid TLS certificate, pass
110
+ > `--insecure` to bypass verification (use only against endpoints you trust).
111
+
112
+ ## Feature overview
113
+
114
+ - Connection management: `connect`, `disconnect`, `logout`, heartbeat keep-alive,
115
+ automatic reconnect.
116
+ - Messaging: send/receive for private, group, chatroom and public-channel
117
+ conversations.
118
+ - Message management: history query, recall, modify, mark-read, search, top
119
+ messages.
120
+ - Conversation management: list, unread count, top, mute, tags.
121
+ - User & group info: user profile, friend profile, group info, online-status
122
+ subscription.
123
+ - Chatroom: join/quit, chatroom messages, attribute sync.
124
+ - RTC: create / join / query / quit RTC rooms.
125
+ - File: credential query via `get_file_cred`.
126
+
127
+ ## Quick start
128
+
129
+ ### 1. Create a client
130
+
131
+ ```python
132
+ from imbot_sdk import ImBotClient
133
+
134
+ client = ImBotClient("wss://127.0.0.1", "your-appkey")
135
+ ```
136
+
137
+ Notes:
138
+
139
+ - Pass the base `address`; the SDK appends the path automatically to form
140
+ `ws://host/imbot` or `wss://host/imbot`.
141
+ - `client.platform` defaults to `"Bot"`.
142
+ - `client.auto_reconnect` defaults to `True`.
143
+
144
+ ### 2. Connect
145
+
146
+ ```python
147
+ code, ack = client.connect("your-token")
148
+ ```
149
+
150
+ Notes:
151
+
152
+ - On success `code == ClientErrorCode.SUCCESS`.
153
+ - After success `ack.userId` is written to `client.user_id`.
154
+ - `connect` may only be called while disconnected; a repeated connect returns
155
+ `ClientErrorCode.CONNECT_EXISTED`.
156
+ - After an explicit `disconnect()` or `logout()`, auto-reconnect stops.
157
+
158
+ ### 3. Two ways to receive messages
159
+
160
+ Prefer the high-level listener (you receive a decoded `Message`):
161
+
162
+ ```python
163
+ from imbot_sdk import MessageListener, messages
164
+
165
+ class MyListener(MessageListener):
166
+ def on_message_receive(self, msg):
167
+ if isinstance(msg.msg_content, messages.TextMessage):
168
+ print("text:", msg.msg_content.content)
169
+
170
+ client.add_message_listener(MyListener())
171
+ ```
172
+
173
+ If you want the raw protobuf payloads:
174
+
175
+ ```python
176
+ client.on_message_callback = lambda down_msg: print(down_msg)
177
+ client.on_stream_msg_callback = lambda stream_msg: print(stream_msg)
178
+ ```
179
+
180
+ ### 4. Send a message
181
+
182
+ ```python
183
+ from imbot_sdk import Conversation, messages
184
+ from imbot_sdk.pb import appmessages_pb2 as pb
185
+
186
+ text = messages.TextMessage("hello from imbot-sdk-python")
187
+ up = client.build_up_msg(text, client_uid="bot-1")
188
+
189
+ conv = Conversation(conversation_type=pb.Private, conversation="target-user-id")
190
+ code, ack = client.send_message(conv, up)
191
+ # ack.msgId / ack.msgSeqNo carry the server's acknowledgement
192
+ ```
193
+
194
+ `build_up_msg` encodes a content model into `UpMsg.msgType / msgContent /
195
+ flags`; you can also construct a `pb.UpMsg` directly.
196
+
197
+ ## Full example: connect, listen, echo
198
+
199
+ ```python
200
+ import time
201
+ from imbot_sdk import ImBotClient, Conversation, MessageListener, ClientErrorCode, messages
202
+ from imbot_sdk.pb import appmessages_pb2 as pb
203
+
204
+ class Echo(MessageListener):
205
+ def __init__(self, client):
206
+ self.client = client
207
+ def on_message_receive(self, msg):
208
+ c = msg.msg_content
209
+ if isinstance(c, messages.TextMessage) and msg.sender_id != self.client.user_id:
210
+ up = self.client.build_up_msg(messages.TextMessage("echo: " + c.content),
211
+ client_uid="echo-%d" % time.time_ns())
212
+ self.client.send_message(msg.conversation, up)
213
+
214
+ client = ImBotClient("wss://127.0.0.1", "AppKey")
215
+ client.add_message_listener(Echo(client))
216
+
217
+ code, ack = client.connect("your-token")
218
+ assert code == ClientErrorCode.SUCCESS
219
+ print("connected:", ack.userId)
220
+
221
+ # Proactively send a private message to a user
222
+ up = client.build_up_msg(messages.TextMessage("hi"), client_uid="greet-1")
223
+ client.send_message(Conversation(conversation_type=pb.Private, conversation="target-user-id"), up)
224
+
225
+ try:
226
+ while True:
227
+ time.sleep(1)
228
+ except KeyboardInterrupt:
229
+ client.disconnect()
230
+ ```
231
+
232
+ A runnable version lives in [`examples/echo_bot.py`](examples/echo_bot.py). The
233
+ recommended way is `.env` + the launch script:
234
+
235
+ ```bash
236
+ cp examples/.env.example examples/.env
237
+ # edit examples/.env, at least set IMBOT_TOKEN (IMBOT_TARGET optional)
238
+ bash examples/run.sh
239
+ ```
240
+
241
+ `run.sh` loads `examples/.env` and starts the echo bot; extra arguments are
242
+ passed through (e.g. `bash examples/run.sh --target some-user-id`). You can also
243
+ skip the script and pass args / export env vars directly:
244
+
245
+ ```bash
246
+ python examples/echo_bot.py --token <your-token> --target <target-user-id>
247
+ ```
248
+
249
+ Configuration (env vars / `.env`):
250
+
251
+ | Variable | Description | Required |
252
+ | --- | --- | --- |
253
+ | `IMBOT_TOKEN` | User token | Yes |
254
+ | `IMBOT_TARGET` | Target user id to greet on startup | No |
255
+ | `IMBOT_ADDRESS` | WebSocket base address | No |
256
+ | `IMBOT_APPKEY` | App AppKey | No |
257
+
258
+ Additional notes:
259
+
260
+ - Use `pb.Private` for private chats, `pb.Group` for groups, `pb.Chatroom` for
261
+ chatrooms, `pb.PublicChannel` for public channels.
262
+ - Chatroom messages can also be sent via `client.send_chatroom_msg(chatroom_id, up_msg)`.
263
+ - A received `msg.msg_content` is already decoded by content type, so you can
264
+ `isinstance`-check it directly.
265
+
266
+ ## Message types
267
+
268
+ Built-in content models live in `imbot_sdk.messages`:
269
+
270
+ | Type | Identifier | Class |
271
+ | --- | --- | --- |
272
+ | Text | `jg:text` | `TextMessage` |
273
+ | Image | `jg:img` | `ImageMessage` |
274
+ | File | `jg:file` | `FileMessage` |
275
+ | Video | `jg:video` | `VideoMessage` |
276
+ | Voice | `jg:voice` | `VoiceMessage` |
277
+ | Stream text | `jg:streamtext` | `StreamTextMessage` |
278
+ | Recall notice | `jg:recallinfo` | `RecallInfoMessage` |
279
+ | Merged message | `jg:merge` | `MergeMessage` |
280
+ | Thumbnail-packed image | `jg:tpimg` | `ThumbnailPackedImageMessage` |
281
+ | Snapshot-packed video | `jg:spvideo` | `SnapshotPackedVideoMessage` |
282
+
283
+ For content types that are not built in, the SDK falls back to
284
+ `messages.UnknownMessage`. The JSON encode/decode fields of these models are
285
+ stable, so messages interoperate with the other JuggleIM client SDKs.
286
+
287
+ ## Common APIs
288
+
289
+ > See [`docs/API.md`](docs/API.md) for the full method list. Naming follows
290
+ > Python conventions (snake_case).
291
+
292
+ ### Connection & state
293
+
294
+ `connect(token)`, `reconnect()`, `disconnect()`, `logout()`, `ping()`,
295
+ `add_connection_status_change_listener(listener)`
296
+
297
+ ### Messages
298
+
299
+ `send_message(conversation, up_msg)`, `qry_history_msgs(req)`, `recall_msg(req)`,
300
+ `modify_msg(req)`, `mark_read_msg(req)`, `msg_search(req)`,
301
+ `msg_global_search(req)`, `set_top_msg(req)`, `del_top_msg(req)`
302
+
303
+ ### Conversations
304
+
305
+ `get_conversation(req)`, `get_conversations(req)`, `sync_conversations(req)`,
306
+ `clear_unread_count(req)`, `set_conversation_top(req)`, `set_mute(req)`,
307
+ `delete_conversations(req)`
308
+
309
+ ### Users, groups, status
310
+
311
+ `fetch_user_info(user_id)`, `fetch_group_info(group_id)`,
312
+ `fetch_friend_info(friend_user_id)`, `get_user_status(req)`,
313
+ `subscribe_user_status(req)`, `unsubscribe_user_status(req)`
314
+
315
+ ### Chatroom
316
+
317
+ `join_chatroom(chatroom_id)`, `quit_chatroom(chatroom_id)`,
318
+ `send_chatroom_msg(chatroom_id, up_msg)`, `set_attributes(chatroom_id, attributes)`,
319
+ `remove_attributes(chatroom_id, keys)`
320
+
321
+ ### RTC
322
+
323
+ `create_rtc_room(req)`, `join_rtc_room(req)`, `qry_rtc_room(room_id)`,
324
+ `quit_rtc_room(room_id)`, `rtc_invite(req)`
325
+
326
+ ### File
327
+
328
+ `get_file_cred(req)`
329
+
330
+ > The SDK provides file credential queries; the actual file upload/download flow
331
+ > must be handled by your application against your storage service.
332
+
333
+ ## Usage notes
334
+
335
+ - `publish` and `query` both require the connection state to be `CONNECTED`,
336
+ otherwise they return `ClientErrorCode.CONNECT_CLOSED`.
337
+ - Sends and queries wait up to 10 seconds for an ACK, returning
338
+ `ClientErrorCode.SEND_TIMEOUT` / `ClientErrorCode.QUERY_TIMEOUT` on timeout.
339
+ - The SDK handles heartbeats automatically; if no downstream data arrives for
340
+ more than two heartbeat windows (~20s), it disconnects and tries to reconnect.
341
+ - Message listener callbacks run on internal threads — avoid long blocking work,
342
+ and hand off to a worker queue if needed.
343
+ - Image / file / voice / video content models only encode/decode content; they
344
+ do not upload the underlying files.
345
+
346
+ ## Regenerating protobuf
347
+
348
+ The `.proto` sources live in [`proto/`](proto/). Regenerate after editing:
349
+
350
+ ```bash
351
+ bash proto/gen.sh
352
+ ```
353
+
354
+ ## Module layout
355
+
356
+ | Module | Description |
357
+ | --- | --- |
358
+ | `imbot_sdk.ImBotClient` | Main client entry point |
359
+ | `imbot_sdk.ClientErrorCode` / `ConnectState` | Error codes and connection state |
360
+ | `imbot_sdk.models` | Domain models (`Conversation` / `Message` / `UserInfo` …) |
361
+ | `imbot_sdk.messages` | Message content models |
362
+ | `imbot_sdk.pb` | Protobuf types (`appmessages_pb2` / `connect_pb2` / `chatroom_pb2` / `rtcroom_pb2`) |
363
+
364
+ ## License
365
+
366
+ [LICENSE](LICENSE) (Apache-2.0)