Page: Unichat API
2019-12-13 17:12
-
Unichat API
- Available endpoints
- API Conventions
- Unichat API data types
- Common methods
- User methods
- Contacts
- Private chats
- Groups
- Administration
- Unichat notifications
- Organizations
- Tasks
Unichat API
This is a new service, an API-based fast and lightweight chat service, compatible with the legal requirements of most countries. The chat has a core service, chat engine, that can be used in any Universa-connected projects freely.
The API is an RPC interface available through different media and connections, such as native sockets, web sockets and the HTTP REST endpoint, which all share access to the same RPC interface, documented below.
Each RPC call has a procedure name and a hash (aka map/dictionary) of named arguments, and returns another suchmap of named parameters, possibily empty, or error information.
Available endpoints
Currently, Unichat API could be accessed using:
The latter uses the
To access API the application needd APP_DOMAIN_TOKEN
exmplained in
chat application domain section.
API Conventions
The API functions are specified in the following form:
some_method({foo: <string>,bar: <timestamp>}) -> {result: <string>}
Which means:
- the callable method
some_method
(with websock unichat endpoint also available assomeMethod
) - it takes 2 named arguments,
foo
of typestring
andbar
of type timestamp which all are explained in detail later - it returns a single named value,
result
, which is astring
.
Unichat API data types
The api is multiplatform and so are its data types.
name | type |
---|---|
<int> |
integer signed value of 32 bits |
<long> |
integer signed value of 64 bits |
<decimal_string> |
decimal value (like float number but of higher precision) |
<timestamp> |
date and time in |
If the type is prepended with opt_
prefix, e.g. <opt_string>
, it means
that corresponding parameter may be omitted or missing.
Common methods
These are general-purpose methods:
ping
ping(string: <string>) -> {pong: string}
Use it to test the connections. There is, basically, no need to ping the connection to keep it alive unless specified in the connection endpoint.
version
version() -> {version: <string>, api_level: <int>, min_api_level: <int> }
Additional to the well-known version string, this call returns 2 numbers that are of importance to the application programmer:
api_level
: current api level, the number that grows every time the API suffers significant change. When it is greater than one the application expects, it is time to consult the docs and see what has been changed and might needed to be updated in the applicaiton code.min_api_level
: the minumum api level which is generally compatible with current state of the API.
While we will do our best to make API as backward compatible as possible, with
time some things may require incompatible changes, so the application detected
the min_api_level
being above its expectation should ask user to updgrade immediately.
User methods
Chat User record
Is returned in may chat API objects. The own record has more fields as most user information is not disclosed to others.
{
"user": {
"id": 290,
"nick": "test_user_1",
"is_online":false,
"status":nil,
// private fields:
"email": "[email protected]",
"searchable_nick": true,
"confirmed_at": "2018-10-14T22:40:55Z"
"auth_token": "ZWuzrbsnXivG6J1wrhagWEhDDR_kfigGzIOCRbyLNA_ZiYBDepngE2Xv2",
"avatar": {"full":<string_url>,"small":<string=_url>} // or null
}
fields marked as private are only shown to the user in me
call. The rest
are public fields visible to anybody. If confirmed_at
is null, the email
confirmation should be passed as soon as possible. Avatar, if present, has
always 2 images: full and probably reduced, could be the same if original
image was too small.
You can use is_online
attribute to check whether user is online or offline.
Available status
attribute values: NULL, "away", "busy" and "do_not_disturb"
Registration
register(email:<string>,password:<string>)
-> {user: <<chat user record>}
If email is invalid or already taken, returns error with code: 'invalid_email'
.
Any other registration error return code registration_error
wil a text
contained
detailed error description. Also too weak password may cause errors.
It user is registered successfully, the chat user record is returned and an email with confirmation instruction is sent. User must be advised to confirm it as soon as possible as most methods required confirmed email.
Note that after confirming email user will be redirected to the root URL specified in the unichat application domain.
Resending confirmation instructions
In the case user has not received it, it is allowed to send instructions, not too often.
resend_confirmation(email: <email>) -> {}
returns empty hash on success. Could return error code: 'please_wait'
if reconfirmation was
sent recently.
Reset password
It is possible to reset the password using the email. The procedure contains following steps:
- API requests password reset with
request_password_reset(email: <user_email>)
. - Serive checks the email and send instructions to user by email
- User visits the link in the received email
- Client applications is called with the URL containing
password_reset_token
in a query part of the URL. - Client application extracts the link and calls
reset_password(token:, new_password:)
Detailed step description follows.
Requesting password reset
request_password_reset(email: <strgin> ) -> {}
Email should be a valid email of existing account of the error will be reported.
Nothe the password reset can't be requested too often or the rate_exceeded
error will be returned. The error text will contain time limit for the next
password reset request call.
Password reset callback
The application can set the password reset callback separately from its root URL in the unichat application domain settings page. If it is not set, then the root URL will be used.
The reset callback will have query part containing password_reset_token
parameter
which should be extracted. For example, the callback can be:
https://acme.com/reset_password?password_reset_token=ulkjn5g4rkjbhweh67
in this example the password reset callback in the control panel is set to
https://acme.com/reset_password
, and the token is ulkjn5g4rkjbhweh67
Note that pasword reset callback URL presented in the unichat application domain control panel must not contain any query part.
Resetting the password
The application extracts the passwort reset token from the callback as described above. Havin this token it should ask user to enter and confirm new password, then call API to reset it:
reset_password(token: <string>,password: <string>) -> {}
After this, the client software can login as usual using the new password.
Logging in
Password login
login(email: <string>,password:<string>) -> { user: <chat_user_record> }
Typical error codes are auth_failed
and please_wait
if called too frequently.
Token login
It is possible to login also with the auth_token
returend in own
login(auth_token:<auth_token_string>) -> { user: <chat_user_record> }
The auth_token
is returned in own iser record returned by login()
and
me()
calls.
Logging out
If for some user application needs to leg out current user (logging in required)
logout() -> {}
Update self
Requires logged in user.
update_me(nick: <opt_string>,password: <opt_string>,
searchable_nick:<opt_boolean>,
current_password: <opt_string>,
avatar: <data_url>)
-> { user: <user_record> }
Any parameter may be missing. Only and all fields presented in arguments and not null willbe changed. Any number of fields could be changed with the single call. If a null value will be passed or field will not be listed in arguments, it won't be changed.
For example, to set searchable_nick' to false, use
seachable_nick: false, not
null.
The password should _always_ be accompained with
currentpasswordor error
with
invalidcode` password will be returned.
Typical errors are: invalid_params
if a not allowed field presented and
invalid_nick
if the new nick is bad or in use. If the error occurs, no data will be
changed. Nick limitations as for now are:
- nick must start with a letter
- nick can not have spaces before or after (will be stripped if any)
- nick should be at least 5 characters long
- nick should not contain more than one consequent spaces
Please note that
Successful profile update sends notification
{event:"changed",object_type:"user",object:{<user_record>}
to all relevant users including self, e.g. to all users that have groups in common with this one (and thus may require redraw on clients).
User status
Requires logged in user.
update_status(status:<string>)
Possible invalid_user_status
error if the value is not NULL, "away", "busy", "do_not_disturb" or "invisible".
If the status is "invisible", then the user has information that he is offline and notifications about this user's activity in the chat are not transmitted.
Avatar format
The avatar optional parameter described above must be a
data url string,
containig all the fields, e.g. data:<mediatype>;base64,<data_base64>
. No part
could be omitted. mediatype should be image/..., we support png
and jpeg
for sure
and many other formats that we do not reccomend to use.
It should not be too big, we will impose restrictions in near future, the avatar should be below 2M anyway.
Get self
Requires logged in user.
me() -> { user: <user_record> }
Get the SSE authorization code
This code is only needed to get SSE events, which could be used with HTTP REST endpoint. With Websock endpoint it is useless as events are already transmitted via same connection with API calls.
To create/get existing code:
api_create_sse_auth_code() -> { code: <string_code> }
The code then could be used with http rest unichat endpoint to subscribe to SSE events.
Delete SSE authorization code
When the clietn software does not want to be able to receive SSE events it could revoke the code by calling:
api_delete_sse_auth_code() -> {}
Push notifications
A push notification is a message that pops up on a mobile device. Push notifications provide convenience and value to app users. For example, users can receive new messages right on their lock screen
Add devise token
For the service to work, you must send the device token. Use this method when the user logs in.
devise_token(token: <string>, notification_service: <string>) -> { user_devise: <user_devise_record> }
Available values for the notification service:
ios_app
- use for the Apple mobile devises.
User must be logged in.
Typical errors are: invalid_params if a not allowed field presented and invalid notification_service
if the notification service is bad or in use. If the error occurs, no data will be changed.
Remove devise token
To remove a token so that the device stops receiving notifications, use:
remove_devise_token(token: <string>, notification_service: <string>) -> { result: true }
User must be logged in. Use this method before the user logs out.
Typical errors are: invalid_params if a not allowed field presented and invalid notification_service
if the notification service is bad or in use. If the error occurs, no data will be changed.
Get user
Use it to refresh other user public data. As for now, all this information is already included in contacts/subscriptions.
get_user(user_id:<long>) -> { user: <user_record> }
This method does not require authentication.
Contacts
Contacts are represented by <contact_record>
:
{
"contact": {
"blocked_at": null, // not null if blocked
"confirmed_at": null, // not null if confirmed
"created_at": "2018-10-14 23:10:10 UTC",
"updated_at": "2018-10-14 23:10:10 UTC",
"user": {
"id": 517,
"nick": "test_user_2"
}
}
Adding contact
add_contact(email: <opt_string>,nick: <opt_string>,user_id: <opt_long>)
-> {contact: <contact_record>}
Only one of email
, user_id
or nick
must be presented.
On success, return contact record. If no contact found returns code: not_found
error.
The just added contact is in "unconfirmed state". It will become confirmed when other party will confirm it by adding it to contacts.
Other party will see the nick of the party which has called add_contact
first
and therefore could use it to add it back. When both perties have added each other,
the contact state will turn to confirmed.
Unconfirmed contacts should be shown differently and could have different notifications policy from confirmed ones.
Another way of adding contact is creating a provate chat to it. It will add the contact automatically.
Deleting contact
delete_contact(email: <opt_string>,nick: <opt_string>,user_id: <opt_long>)
Only one of email
, user_id
or nick
must be presented.
On success, return true
. If no contact found returns code: not_found
error.
List contacts
get_contacts() -> { contacts: [<contact_reocrd>,...] }
deprecated form also works:
contacts() -> { contacts: [<contact_reocrd>,...] }
but we ask to remove it from code, as it will be removed soon.
Get last activity time
get_last_active_at(user_id: <long>) -> { last_active_at: <time_string> }
returns last known activity time of the user, or null
of current user has
no access to this information.
Searching known users
Known usrers are these to whom currently logged in user has at least one group in common, e.g. where both are participants.
To seacrh for a nick knowning it part (not ncecessarily the beginning):
search_knwon(nick_part: <string>)
-> { users: [<user_record>,..], overflow: <bool> }
nick_part
should not be too short (right now the limit is at least 5 characters).
invalid_parameter
api error is reported if it is too short.
users
: array of chat user record, possibly emptyoverflow
: true means that there are more matches that are presented. Narrow the search.
Global user search
It is possible to search for nicks with at least 5 first characters:
search_user(nick_prefix: <string>)
-> { users: [<user_record>,...], overflow:<boolean> }
users
: array of chat user record, possibly emptyoverflow
: true means that there are more matches that are presented. Narrow the search.
Private chats
These are special kind of unichat groups reserved for private conversations. The private chats cannot be administered (moderated) and do not support inviting participants. Only a single private chat could exist between a given pair of users.
Create/get private chat
get_private_subscription(user_id: <long>) -> {subscription: <subscription_record>}
Returns subscription to the private group for current and specified user, creating it if need. See also unichat subscriptions. The returned structure may look like:
{
"subscription": {
"id": 6062,
"group_id": 1028,
"user_id": 3768,
"role": "rw",
"mute_until": null,
"draft": null,
"last_read_message_id": null,
"created_at": "2018-10-23T13:57:10Z",
"deleted_at": "2018-10-23T13:57:10Z",
"group": {
"id": 1028,
"name": null,
"is_deleted": false,
"created_at": "2018-10-23 13:57:10 UTC",
"updated_at": "2018-10-23 13:57:10 UTC",
"type": "private_chat",
"icon": null,
"participants": [
{
"id": 6062,
"group_id": 1028,
"user_id": 3768,
"role": "rw",
"mute_until": null,
"draft": null,
"last_read_message_id": null,
"created_at": "2018-10-23T13:57:10Z",
"user": {
"id": 3768,
"nick": "test_user_1",
"avatar": null,
"is_online": false
}
},
{
"id": 6063,
"group_id": 1028,
"user_id": 3769,
"role": "rw",
"mute_until": null,
"draft": null,
"last_read_message_id": null,
"created_at": "2018-10-23T13:57:10Z",
"user": {
"id": 3769,
"nick": "test_user_2",
"avatar": null,
"is_online": true
}
}
]
},
"user": {
"id": 3768,
"nick": "test_user_1",
"avatar": null
}
}
}
Notice subscription.group.type
above.
There are always only 2 participants and chat type is private_chat
. Use it
as a usual unichat group to write, list and change messages.
Remove from chat list
Important. UI can not delete private chat group or unsubscribe from it. Instead, a party can block the other one, or mute the notifications, to achieve the same effect. We advise just not to show the private chats for blocked users.
Likewise, to remove from the chat list, you can use:
remove_private_subscription(user_id:<long>)
-> { subscription: [<<unichat subscription record>,...]
Successful removal subscription sends notification:
{ event: "deleted", object_type: "subscription", object: {<subscription_record> }
Recovering to the chat list occurs while calling get_private_subscription
method, or post message to private chat with notification:
{ event: "changed", object_type: "subscription", object: {<subscription_record> }
Groups
Please read unichat groups and unichat subscriptions first to clearly understand what are these and how it works.
Get subscriptions
For each group used is subscribed to there is a corresponding subscription which carries a group information plus user rights in the group and more important data, see more in unichat subscription.
So, instead of querying groups user has access to, client calls for subscriptions:
get_subscriptions(short: false,limit: 0,offset: 1000)
-> { subscriptions: [<<unichat subscription record>,...]
See unichat subscription record for the record structure, and unichat groups and unichat subscriptions for overall explanation.
This command supports paging. Sunscriptions are ordered by subscription.id ascending, so just set
limit
andoffset
to some non-default values to iterate through subscriptions.set
short: true
to get subscription information without group participants. the short form carries all the information to show groups bar, while heavygroup.participants
section is needed only when the group is opened.
We recommend first to scan subscriptions by chunks of 10-100 records in short
mode to quickly display them, and use get_subscription(id)
when opening a group
UI, or scan them all in the background wither way.
Get single subscription
Use it when you need to get or refresh only one subscription with known id
,
for exmaple, after receiving the notification.
get_subscription(subscription_id:<long>)
-> {subscription: <subscription_record>}
It is not equal to get_subscriptions()
as it provides access to the goven id
that can't be done otherwise without scanning all (in worst case) subscriptions.
Create room
A room is a general purpose chat. Creator becomes owner and admins. As ususal with universa chat, the result is the subscription of the creator.
create_room name: <string>, user_ids: []. is_space: <opt_boolean>
-> { subscription: <<unichat subscription record>}
with parameters:
name
: required room name. Any string.user_ids
: optional array ofuser_id
to be invited immediately.is_space
: if not empty, the group attribute is_space will be changed. Default value:false
.
It is possible to create room without users: it will create only creator (which becomes an owner) subscription.
It returns the creator's unichat subscription record as {subscription:
<<unichat subscription record>}
. Creator becomes an admin,
all invited users will get write permissions. IT is possible to add participants
later.
When creating subscriptions for the room, each involved user, including creator, will receive new subscription notificaton, e.g.
{"event": "new", "object_name": "subscription", "object": {<subscription record>}}
Also, new created room will be posted with one unichat system message with
xtag: 'creation'
from room owner and one xtag: 'invite', reference_type: 'user',
reference_id: user_id
for each invited user.
Message record
Carries information about teh chat message. It can be short and long. Short version represents deleted messages, where text and some other fieds are not available. Here is the example:
{
"message": {
"id": 34,
"user_id": 367,
"group_id": 54,
"deleted_at": null, // meaning it is not deleted
"serial": 38,
"text": "hello u2!",
"xtag": null,
"attachment": null,
"attachments": [],
"mentions": [], // or null, or array of mention records
"reference": {type: "user", id: "1"},
"forwarded_message_id": 11, // optional: only if not null
"in_reply_to_message_id": 22, // optional: only if not null
"edited_at": null, // was not edited
"created_at": "2018-10-15T18:33:56Z"
"tasks": [<task_record>]
}
}
attachment
field is obsolete, useattachments
.tassk
- array of tasks. For more information, see task_record.
reply and forward
If the message is the forward of another message, it will have forwarded_message_id
field pointing to the original message. Same way in_reply_to_message_id
if
exists, points to the original message to which this one is a reply. To
create forward/reply just add correspodning ids when posting the message.
Message serial
Every time message is changed, say, its text is modified, its serial
field
gets some new value, which is guaranteed to be bigger than it was. This way
the new and changed messages coudl easily be loaded by calling all messages
with serial
bigger than the last (e.g. greatest) known to the client.
This query will automatically add new and changed messages alltogether.
xtag
String tag user with unichat system messages to specify type of special message.
Reference
With some unichat system messages referenced are used to specify some connected object, in which case its type and id are passed in this field.
Files Attachments
If message has attachments, it will be included into message record with
the attachments
key. for example:
"attachments": [
0 => {
"content_type": "image/jpeg",
"byte_size": 1597, // size in bytes
"url": <download_url_string>, // fownload link
"preview": <preview_url_string>, // only for images
"filename":"test.jpg"
}
],
"attachment": {
"content_type": "image/jpeg",
"byte_size": 1597, // size in bytes
"url": <download_url_string>, // fownload link
"preview": <preview_url_string> // only for images
}
- The
attachment
field is obsolete, but left for compatibility with older versions and contains the latest attachment.
The attachment could be of any type, but previews are only available for images.
Post message
User can post messages to groups where it is subscribed and has write permission.
post(group_id:<opt_long>,subscription_id:<opt_long>,text:<string>,
uid:<string>, attachment:<opt_string>, attachment_filename:<string>, in_reply_to_message_id: <opt_long>,
forwarded_message_id: <opt_long>, upload_id: [],
mentions: [{user_id:<long>,text: <string>},...], task_ids: [<opt_long>])
->{message: <message_record>}
Parameters are:
group_id
ORsubscription_id
: one of two is required. Call it wuth subscripion whereever possible to reduce server load.text
: message text, required as for now.uid
: some generally unique identifier, random string of at least 48 characters is advised. Maximum allowed size is 64 characters. Can use GUID though we do not recommend it as most RNG give better entropy.uid
must be unique for a user ofr medium time intervals.attachment
: if present, must be a valid and full data-url string, e.g.data:<mediatype>;base64,<data>
. For mediatypesimage/*
the system will prepare also preview image automaticlly.attachment_filename
: you can change the name of the transmitted file.in_reply_to_message_id
: if present, must point to the original message to which new one will reply.forwarded_to_message_id
: if present, must point to the original message to which new one will reply.upload_id
: are identifiers of attachments made through theattachment_by_chunks
method. If the string is empty, all attachments will be destroyed. Available: nil, empty string, array, number.mentions
: optional array of mentions, see below. important do not passnull
, just omit this field if not needed.task_ids
: optional array of tasks. Attaching tasks to message
Returned value is a {message: <<message record>}
containing a created message.
It is safe to call it repeatedly with the same message and same uid
, no
duplication will happen and the proper message object will be returned.
Creating messages cause notification to be sent to all group members, therefore, the postin user will also receive it. Notification will arive at any time, before (unlikely) or after the call returns.
Important note. Notification passed to the message owner connection will
contain object.uid
field first few hours after message creation at least.
Other recipients or past the time this field could be null or omitted.
Large attachment
If the attachment is large, it is necessary to upload the file in chunks.
attachment_by_chunks(file_chunk:<string>, upload_id:<long>, file_size:<long>, file_checksum:<int>, file_name:<string>, async:<boolean>, chunk_id:<integer>)
-> {upload_id:<long>, file_size:<long>, file_checksum:<int>, content_type: <opt_string>, voice_recognition: <opt_boolean>}
with parameters:
async
: file sending format (default: false). Ifasync = true
then thechunk_id
field is required. Chunks can be sent in a different order. When assembling the file, the pieces will be combined into a single file, sorted bychunk_id
in turn. Ifasync = false
, then the sending of the next chunk should be done after successfully receiving a response about sending the previous chunk.file_chunk
: a chunk of file encoded in Base64. Ifasync=falsе
then the size of the chunk should be 64,000 bytes, otherwise ifasync=true
then the chunk should not exceed 1 megabyte (1048576 bytes).upload_id
: upload file ID; the first time is NULL, the next time is the value that the method returnedfile_size
: file size in bytesfile_checksum
: file checksum in CRC32 for file verificationfile_name
: file name to be assigned after it is fully uploadedchunk_id
: serial number of the chunk (0, 1, 2... etc.). Only ifasync=true
content_type
: if present, sets the content type attribute of the filevoice_recognition
: true or false. True for speech recognition. See "Voice Recognition" below for more information.subscription_id
: only withvoice_recognition
=true
. Subscription where the attachment will be sent.
The file is divided into chunks and uploaded to the server.
Response example:
{
upload_id: 14,
file_size: 14243242,
file_checksum: 3423521938426
}
where:
upload_id
: upload file IDfile_size
: ifasync=false
then the upload file size, otherwise the upload chunk sizefile_checksum
: ifasync=false
then the upload file checksum, otherwise the upload chunk checksum
After the file has been completely uploaded, and the file size and checksum match, the key upload_id
will be included into message record. For example:
"upload_id": 14
or as an array, if several attachments are loadedy:
"upload_id": [14, 21]
Voice recognition
You can attach the file as a message with voice recognition. In the method attachment_by_chunks
you need to pass additional parameters:
voice_recognition
is a boolean variable, ifTRUE
- speech recognition is enable.subscription_id
- ID of subscription where the attachment will be sent. It is necessary in order to check whether the user has the right to use the voice recognition function. This rule is set for the organization as a timestamp to which this right exists.
After the file has been uploaded, you can call the voice_recognition
to start recognizing audio:
voice_recognition(upload_id:<long>)
-> {
result: {
status: "pending"
transcript: NULL,
error: NULL,
created_at: "2019-01-01 01:02:03",
updated_at: "2019-01-01 01:02:03",
audio: {
filename: "audio.ogg",
content_type: "audio/ogg",
byte_size: "2432543",
url: "http://example/audio.ogg",
}
}
}
And call the voice_recognition_result
method to get recognition results.
voice_recognition_result(upload_id:<long>)
-> {
result: {
status: "completed"
transcript: "Hello world",
error: NULL,
created_at: "2019-01-01 01:02:03",
updated_at: "2019-01-01 01:02:03",
audio: {
filename: "audio.ogg",
content_type: "audio/ogg",
byte_size: "2432543",
url: "http://example/audio.ogg",
}
}
}
If the status
= pending
, then processing is still in progress.
If the status
= failed
, the error
field must be filled.
If the status
= completed
, the transcript
field must be filled.
Mentions
Mentions are @somebody
-style mentions of some group participant in the message.
The client must detect them (could be in any form) and fill the mentions array
as stated above, where text
is a subsctring in the source message, that the
client could use to highlight or substitute to the link, and user_id
is the
id of the mentioned user as it was when the message was comosing.
This was different clients could properly show and process mentions despite on the format and algorythm of mentions entering/detecting, and does not depends on the users changing their nicks in futire.
Mentions are reported in the message record as .mention
array. Also
the system will set subscription.last_mentioned_in_message_id
accordingly. This
chainge of the subscription will not trigger subscription change notification,
as the recipient will already get the new message notification, which will
contain mentions
so the client software could derive the necessary information
of it, so issuing separate subscription change is redundant.
Why uid?
When client software attempts to post a message, it may happen it will not arrive to the service, or the service couls be in error state, ir, worst of all, the client may not receive acknowledgment that the message was actually created.
When client software does not receive answer for the post()
call, it should
retry until succeeded. It may therefore cose unintentional message duplication
when the server has actually posted the message but the client software did not
receive result. Simplest is the user has get out of mobile internet coverage.
To avoid it, the client software should generate a more or less unique random
string, uid
and store it locally with the message, posting it on every try.
This way the system will detect and ignore unintentional duplications.
Load messages
This function allow reading exisitng messages in a group with paging in tow modes: get most recently created and get created and added after some point. It requires authenticaion.
get_messages(subscription_id:<opt_long>,group_id:<opt_long>,
limit:100,offset:0, before_id:<opt_long>,after_serial:<opt_long)
-> { messages: [<<message_record>,...] }
Parameters are:
group_id
orsubscription_id
: the group to read messages from, current user must have read access to it. Please use subscription_id where possible.limit
andoffset
allow paging in usual sensebefore_id
if present, select messages that are older than a given id, in most recent first.after_serial
if present, select messages that are created and modified path one with such serial number, most recent last.- if neither
before_id
notafter_serial
are specified, selects most recently created messages, most recent first.
In other words, to get latest messages, specify no selection arguments and use
offset
, otherwise use before_id
which is roughly the same. If you want to
pull all the messages and their changes, pull it all using after_serial
using
the biggest serial you have preloaded, and you will get them all and most recent
versions of them too.
Get single message
It may happen, for example, when received new message notification, get a message just by its id, bypassing looking up the subscription for its group:
get_message(message_id:<long>) -> {message:<message_record>}
Edit own message
Editing own messages is only allowed within certain time period after its creation.
Message editing does not prolong this period. Edited messages has non-null
last_edited_at
field so edited messages can be shown in a different way. The
system does not keep the message edition history, so it is the only evidence
that the text was changed.
edit_message(message_id:<long>, text:<opt_string>, attahcment: <opt_string>,
clear_in_reply_to_message_id: false, task_ids: [<opt_long>])
-> {message: <message_record>}
requires at least one of attachment
and text
. Use empty string for each to
clear it without deleting the message. I we not recommend to leave empty messages,
the system might decide to delete completely empty messages of regular type.
text
: if present, changes message text. Pass empty string to delete the text only. Passnull
to leave text unchanfed.attachment
: if present, changes the attachment. must be a valid and full data-url string, e.g.data:<mediatype>;base64,<data>
. For mediatypesimage/*
the system will prepare also preview image automaticlly. Use empty string "" to delete attachment keeping the message. Passnull
leave attachment unchanged.clear_in_reply_to_message_id
if set to true, dropsin_reply_to_message_id
. Note that currently it can not be set to anything but dropped while editing the message.task_ids
optional array of tasks. if present, attach the tasks to the message. Empty string for detach a task from a message.
It will broadcast notification of message change to a group, e.g.
{event: 'changed', object_type: 'message', object: <message_record>}
Notice that the updated message has increased serial
field value (by some
unknown positive number), so it is possible to get new and edited messages
alltogether as descibed above in "load messages".
Delete own message
Could be done by the author at any time. This operation is irreversible.
delete_message(message_id: <long>)
-> {message: <message_record>
If the message is already deleted, it is not changed, error is not reported.
Note that the deleted message record contain less information: it has no text,
attachemt, edited_at and created_at
fields.
It will broadcast notification of message deletion to a group, e.g.
{event: 'deleted', object_type: 'message', object: <message_record>}
Manage own subscription
Susbscrption have several fields writable by its owner that allow implement better UX:
last_read_message_id
: set it to the message id that was likely read by the user. Setting this field may notify other group members about it. Note that service does not check the value against message ids so you can write there zero, negative value and whatever you might find useful.draft
: save here the text entered by the user to not to loose it. Convenient way to share partially written message among sessions and devices. Set to empty string to clear it.tags
: allow tag the subscription with an array of string tags. This is per- subscription (not per-group which also exists) tagging, allowing each user keep some information joined with the subscription. Tags are not visible to others.mute_until
: if set to some time, notifications to this subscription should not be shown to the user clearly. This setting can only be interpretated by the client software.
To change it:
update_subscription(subscription_id:<long>,draft:<opt_string>,
last_read_message_id:<opt_long>,
mute_until:<opt_is08601_datetime>)
-> { subscription: <subscription_record> }
Only supplied fields will be changed. If subscription is changes, changed
notification for it will be sent to the owner or to the group, depending on
change relevance. For example, last_read_message_id
causes group
broadcasting.
To clear mute_until
set it to any moment in past. Setting to null will not change
its value.
Setting last_read_message_id
will also clear last_mentioned_in_message_id
if it is lesser or equal to newly set last_read_message_id
.
Invite to the room
Regular rooms by default allow everybody with write permission to invite others to the room with:
invite(subscription_id:<long>,user_id:[<long>])
-> {subscription:<subscription_record>}
where
-subscription_id
is a subscription of the current user to the room to which
he or she wants to invite
user_id
: id or array of the users IDs which should be invited to the room.
on success, returns the subscription of newly added user, send a notification
of the new subscription and post a unichat system message from inviter
with xtag:"invite"
and reference
pointing to the new user. Of course posting
message causes also new message notification.
Use invite code
If the user has the invite code, it should use it. See unichat invite codes for explanations on how to obtain and process it with the client software.
use_invite_code(code:<string_code>) -> {}
There is no returned data on successful call. Instead, the application will receive notifications and messages depedning on the code.
When user joins the group using the invite code, all participants (include new one)
will receive usual new subscription event. Also system posts unichat system message
from new user with xtag:"joined"
.
Leaving the room
To do it, just unsubscribe your subscription:
unsubscribe(subscription_id:<long>) -> {}
On successful unsubscription, the notification of a deleted object is propagated among the group subscribers:
{
event:'deleted',
object_name:'subscription',
object: {id: deleted_subscription_id}
}
Notice that on the deleted object notification, only the id
field is guaranteed to
be present, the other fields may be all omitted.
Also, the system posts unichat system message from leaving user with xtag: leave
just before unsubscribing, so the leaving user will receive its notification.
Please note that after unsubscribing the user may loose access to the group entirely depending on its nature and settings.
When the group owner leaves
A group can not exist without owner, who is its unrevokable admin. The only way for owner to dismiss is to call unsubscribe. It is not possible to remove owner's admin role or kick him or her out of the group. So when the owner leaves, the group risks to get the failed state with no admins left.
So when the owner leaves by their good will, the system tries to find the best candidate for this role (the oldest administrator, or oldest writer or oldest reader without mutes and bans), and assign it on this role. The subscription of the new role is promoted to the admin level if need. The usual subscription change notification is sent.
When the last participant leves
The group is destroyed by the system if there are no more participants. No archived groups exist at this time.
Administration
The user having subscription with admin
role can administrate the group with
methods described below.
Change other user role
Unless other user is a group owner, admin can change other users roles, also promoting them to admin (this could be changed in group settings later).
set_access(subscription_id:<long>,user_id:<long>,role:<string)
->{subscription: <updated_user_subscription> }
Where subscription_id
is the subscription of the admin to the group to
update and user_id
is the user to update access. Valid roles are: ro
, rw
and admin
.
When access is changed, temporary readonly mode and temporary ban are cleared.
Owner access can not be changed.
Kick user from a group
kick(subscription_id:<long>,user_id:<long>)
-> {}
Where subscription_id
is the subscription of the admin to the group to change.
It is not allowed to kick owner out of the group. Other admins can be kicked out as well.
Successful kick sends system message xtagged kick_out
.
Change group appearance
update_group(subscription_id:<long>,name:<opt_string>,icon: <opt_string>, pinned_message_id: <opt_long>)
-> { group: <<unichat group record> }
name
: if not empty, the group name will be changed.icon
: if not empty, the group icon will be reset. Icon data should be a complete image data url, e.g.data:image/<subtype>;base64,<data>
. We recommend use onlyjpeg
for photo images andpng
for graphic art.pinned_message_id
: message ID. If not empty, pinned message will be changed. To delete, use an empty string.
See unichat subscription record for group record sample.
Create group invite code
The group admin can create a code that allow anybody to join the group. See unichat invite codes for details how to use them.
create_group_invite_code(subscription_id:<long>)
-> { code: <string> }
If code is already set, this call returns it. To change code, administrator must delete existing code first, them create new one.
The invite code is also available to all participants, if present, it will be included in every group object as:
group: {
// ...
invite_code: <string>
// ...
}
This field will be set to null
if the code is not set/deleted. The field
could be also missing in some circumstances.
Delete group invite code
Group admin can delete the invite code (see unichat invite codes]):
delete_group_invite_code(subscription_id:<long>) -> {}
If there was no code, it does nothing.
Unichat notifications
Whenever something happen on the server, it notifies relevant clients about it by sending the notification.
Receiving notifications
The client side receives notifications in the RPC it could provide. The signature
of the noticiation target should be (javascript example, in localInterface
object):
onNotificationReceived: function({notifications:...}) {
}
This function, it presented in localInterface
will be called by the server
each time a relevant event occur. The functions receives notifications
named
argument, and recevies named argument notifcations
with array of notification records
explained below.
Notification data
Each notification has 3 mandatory fields:
event
: could benew
,changed
, ordeleted
, or some exotic types discussed later on.object_type
: the type of the referenced objectobject
: the referenced object with minimum information about it, as we want to reduce the size of the events stream. Client can load object in full details with corresponding API methods as need.
Here is the real world example:
{
"notifications": [
{
"event": "new",
"object_type": "message",
"object": {
"id": 8,
"user_id": 72,
"group_id": 2,
"deleted_at": null,
"serial": 8,
"text": "hello world ;)",
"edited_at": null,
"created_at": "2018-10-15T17:54:41Z"
}
}
]
}
Note that notifications come in Array, most often, of 1 element size. Still it may happen to have more than 1. In that case, first item of the array represent the most old notification, and the last - the most recent one.
Special notifications
Events that are not just changed state of some object are described here.
"entering": user enters text
Is emitted to all group participants when some user saved a draft of a message to his subscription. To trigger it. just save currently entered text into a draft in the subscription. Client software should do it by the inactivity timer when user has entered/changed anything in the chat message fileld.
Notification has following format:
{
event: "entering",
object_name: "no matter", // ignore it
object: {
user_id:383, // user who is typing message
group_id:78 // group where it occurs
}
}
is_online
or is_offline
: user status.
Example:
{
event: "is_online",
object_name: "user",
object: {
"id":87,
"nick":"ivan",
...
}
}
Organizations
Create organization
Creator becomes owner and admins.
create_organization: name: <string>, icon: <opt_string>, brand_color: <opt_string>
-> { organization: <<organization_record> }
with parameters:
name
: required organization name. Any string.icon
: if not empty, the organization icon will be reset. Icon data should be a complete image data url, e.g. data:image/;base64,. We recommend use only jpeg for photo images and png for graphic art. brand_color
: if not empty, set up brand color.
Destroy organization
destroy_organization:(organization_id:<long>)
-> { organization: <<organization_record> }
with parameters:
organization_id
: Organization ID
Change organization appearance
update_organization(organization_id: <long>, name: <opt_string>, icon: <opt_string>, brand_color: <opt_string>, allow_forwarding: <opt_boolean>)
-> { organization: <<organization_record> }
organization_id
: Organization IDname
: if not empty, the organization name will be changed.icon
: if not empty, the organization icon will be reset. Icon data should be a complete image data url, e.g. data:image/;base64,. We recommend use only jpeg for photo images and png for graphic art. brand_color
: if not empty, set up brand colorallow_forwarding
: Allow or prohibit message forwarding, default:false
List of organization users
get_organization_users(organization_id:<long>)
-> { organizations_users: [<<organizations_user_record>,...] }
organization_id
: Organization ID
List of user organizations
get_organizations()
-> { organizations: [<<organization_record>, ...] }
Invite to the organization
Only the administrator has execute rights.
organization_invite(organization_id:<long>,user_id:<long>)
-> {organizations_user: <<organizations_user_record>}
where
organization_id
: Organization IDuser_id
: ID of the user who is invited to the organization
on success, returns the user_organization_record of newly added user, send a notification of the new user in organization.
When a user is added to an organization, he automatically becomes a subscriber to all public chats of organizations.
Change user role
Only the administrator has execute rights.
Unless other user is a organization owner, admin can change other users roles, also promoting them to admin.
organization_access(organization_id:<long>, user_id:<long>, role:<string)
->{organizations_user: <<organizations_user_record> }
organization_id
: Organization IDuser_id
: ID of the user to whom the rights are assignedrole
: Valid valuesadmin
andro
Owner access can not be changed.
Kick a user out of the organization.
Only the administrator has execute rights.
organization_kick(organization_id:<long>,user_id:<long>)
-> {}
organization_id
: Organization IDuser_id
: ID of the user to whom the rights are assigned
It is not allowed to kick owner out of the group. Other admins can be kicked out as well.
The user will exit all chats of the organization.
Leave the organization
organization_leave(organization_id:<long>) -> {}
On successful, the notification of a deleted object is propagated among the group subscribers:
{
event:'deleted',
object_name:'organization',
object: { id: <<organization_id_record> }
}
The user will exit all chats.
Create organization room
Only the administrator has execute rights.
A room is a general purpose chat. Creator becomes owner and admins. As ususal with universa chat, the result is the subscription of the creator.
create_organization_room(name: <string>, user_ids: [<int>,..], organization_id: <int>, is_space: <opt_boolean>, type: <opt_string>)
->{subscription: <<unichat subscription record> }
organization_id
: Organization IDname
: name: required room name. Any string.user_ids
: optional array ofuserid
to be invited immediately.is_space
: if not empty, the group attribute is_space will be changed. Default value:false
.type
: Optional. If optionis_space
is TRUE. Valid valuespublic
andprivate
, default:private
List of organization subscriptions
get_organization_subscriptions(organization_id: <int>, short: <booleab>, limit: <int>, offset: <int>)
->{subscriptions: [<<unichat subscription record>,...] }
organization_id
: Organization IDThis command supports paging. Sunscriptions are ordered by subscription.id ascending, so just set
limit
andoffset
to some non-default values to iterate through subscriptions.set
short: true
to get subscription information without group participants. the short form carries all the information to show groups bar, while heavygroup.participants
section is needed only when the group is opened.
For more information, see unichat subscription record for the record structure, and unichat groups and unichat subscriptions for overall explanation.
Tasks
In organizations, you can create tasks. Tasks can only belong to yourself, or to any group. A task is assigned to one performer, they may also have comments and attachments. When creating a task and specifying the executor, a chat is created with this user and this task is attached to this chat. If the task sets the group and the executor, then the executor must be in this group. Anyone who belongs to the group to which the task belongs can make changes.
Create task
create_task(organization_id:<opt_long>, title:<string>, description:<opt_string>, group_id:<opt_long>, executor_id:<opt_long>, uploads_id:<opt_long>)
-> { task: <<task_record> }
with parameters:
organization_id
: organization IDtitle
: task titledescription
: task descriptiongroup_id
: If present, then attached to the groupexecutor_id
: If present, then it is attached to the group and if thegroup_id
is specified, then the user must be in this groupuploads_id
: are identifiers of attachments made through theattachment_by_chunks
method. If the string is empty, all attachments will be destroyed. Available: nil, empty string, array, number
Update task
update_task(task_id:<integer>, title:<opt_string>, description:<opt_string>, group_id:<opt_integer>, executor_id:<opt_integer>, uploads_id:<opt_integer>, organization_id: <opt_long>, done: <opt_boolean>)
-> { task: <<task_record> }
with parameters:
task_id
: task IDtitle
: task titledescription
: task descriptiongroup_id
: If present, then attached to the group. If the group was previously specified, then you cannot change it.executor_id
: If present, then it is attached to the group and if thegroup_id
is specified, then the user must be in this groupuploads_id
: are identifiers of attachments made through theattachment_by_chunks
method. If the string is empty, all attachments will be destroyed. Available: nil, empty string, array, numberorganization_id
: organization IDdone
: if TRUE, then the task is completed.
Destroy task attachment
To destroy an attachment, you can call the method:
destroy_task_attachment(task_id:<long>, signed_id:<string>)
-> {<task_record>}
with parameters:
task_id
: task IDsigned_id
: special attachment identifier, attribute of attachment record in task
Destroy task
Destroy task with all comments and attachments.
destroy_task(task_id:<long>)
-> {}
with parameters:
task_id
: task ID
When the task was deleted, all attached messages to this task will change the text to To-Do was deleted
.
List of organization tasks
organization_tasks(organization_id:<long>)
-> { tasks: [<<task_record>,...] }
organization_id
: Organization ID
List of group tasks
group_tasks(subscription_id:<long>)
-> { tasks: [<<task_record>,...] }
subscription_id
: Subscription ID
List of user tasks
user_tasks(organization_id:<opt_long>, user_id:<opt_long>)
-> { tasks: [<<task_record>,...] }
organization_id
: Organization ID; if specified, used within this organizationuser_id
: User ID; if not specified, the current user is used
Create sub-task
A task may contain subtasks in the form of a checklist with statuses done/undone.
create_subtask(task_id:<long>, text:<string>)
-> { subtask: <<subtask_record> }
with parameters:
task_id
: task IDtext
: sub-task title
Update sub-task
A task may contain subtasks in the form of a list.
update_subtask(subtask_id:<long>, text:<string>, done:<opt_boolean>)
-> { subtask: <<subtask_record> }
with parameters:
subtask_id
: task IDtext
: sub-task titledone
: status of sub-task (true/false)
Destroy sub-task
A task may contain subtasks in the form of a list.
delete_subtask(subtask_id:<long>)
-> {}
with parameters:
subtask_id
: sub-task ID
Sort order of sub-tasks
subtasks_ordering(task_id:<long>, subtasks:<long>)
-> { task: <<task_record> }
with parameters:
task_id
: task IDsubtasks
: array of identifiers of sub-tasks, example: [6, 3, 5, 4, 1, 2]
Add comment
create_task_comment(task_id:<long>, text:<string>)
-> { task_comment: <<task_comment_record> }
with parameters:
task_id
: task IDtext
: sub-task title
Load comments
task_comments(task_id:<long>, limit:<opt_integer>, offset:<opt_integer>, before_id:<opt_long>)
-> { comments: [<<task_comment_record>,...] }
with parameters:
task_id
: task IDlimit
andoffset
allow paging in usual sense. Defaultlimit
is 50, defaultoffset
is 0.before_id
if present, select comments that are older than a given id, in most recent first.
Destroy comment
A task may contain subtasks in the form of a list.
delete_task_comment(comment_id:<long>)
-> {}
with parameters:
comment_id
: comment ID