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 and offset 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 heavy group.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 of user_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, use attachments.
  • 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 OR subscription_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 mediatypes image/* 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 the attachment_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 pass null, 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). If async = true then the chunk_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 by chunk_id in turn. If async = 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. If async=falsе then the size of the chunk should be 64,000 bytes, otherwise if async=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 returned
  • file_size: file size in bytes
  • file_checksum: file checksum in CRC32 for file verification
  • file_name: file name to be assigned after it is fully uploaded
  • chunk_id: serial number of the chunk (0, 1, 2... etc.). Only if async=true
  • content_type: if present, sets the content type attribute of the file
  • voice_recognition: true or false. True for speech recognition. See "Voice Recognition" below for more information.
  • subscription_id: only with voice_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 ID
  • file_size: if async=false then the upload file size, otherwise the upload chunk size
  • file_checksum: if async=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, if TRUE - 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 or subscription_id: the group to read messages from, current user must have read access to it. Please use subscription_id where possible.
  • limit and offset allow paging in usual sense
  • before_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 not after_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. Pass null 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 mediatypes image/* the system will prepare also preview image automaticlly. Use empty string "" to delete attachment keeping the message. Pass null leave attachment unchanged.
  • clear_in_reply_to_message_id if set to true, drops in_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.