The APIs of Jami

OpenDHT

The documentation related to the API of OpenDHT is here and will not be detailed in the following part.

Daemon

The managers

The API of the daemon is decomposed between 5 Managers + 1 Instance file:

  • The CallManager interface is used to manage call and conference related actions. Since Ring-daemon supports multiple incoming/outgoing calls, any actions involving a specific call must address the method by the means of a unique callID. Ring-daemon will generate a unique callID for outgoing and incoming calls.

  • The ConfigurationManager used to handle the configuration stuff: accounts settings, user preferences, …

  • The PresenceManager is used to track the presence of contacts

  • The VideoManager used to manage video devices and renderers

  • The Instance is used to count the number of clients actually registered to the core. When initializing your client, you need to register it against the core by using this interface.

DBUS

All the documentation and code for the dbus API is located in ring-daemon/bin/dbus.

If you use linux, you can use d-feet when the daemon is running to manipulate the API (or with any another tool).

The LRC project uses this API (and use libwrap on windows and mac os).

JNI

All the documentation and code for the JNI API is located in ring-daemon/bin/jni.

Generation process

  • cd ring-project/

  • Check ./daemon/src/dring

  • Create a new file .i in ./daemon/bin/jni/ from an existing file in the same folder

  • Update with your new interfaces from documentation

  • Check that callback block is at the beginning AND at the end of file

  • cd daemon/bin/jni

  • Edit ./daemon/bin/jni/jni_interface.i and add yours with the other .i files that are in the same folder

  • export PACKAGEDIR=\*\*\*\*/ring-project/client-android/ring-android/libringclient/src/main/java/cx/ring/daemon. ie: export PACKAGEDIR=/home/pduchemin/Documents/ring-project/client-android/ring-android/libringclient/src/main/java/cx/ring/daemon

  • Run

    • ./make-swig.sh

  • or, if you start from scratch

    • ./make-ring.py --init --distribution=Android

    • ./make-ring.py --install --distribution=Android

node js

All the documentation and code for the Node JS API is located in ring-daemon/bin/nodejs. This API is not used in any known project and maybe is not up-to-date.

REST

All the documentation and code for the REST API is located in ring-daemon/bin/restcpp. This API is not used in any known project and maybe is not up-to-date.

Python wrapper

A Python wrapper is available in ring-daemon/tools/dringctrl. This wrapper uses d-bus.

This is, for example a quick and dirty IRC bot to interact with Ring made with this API:

config.json:

{
    "users": {
    },
    "debug": true,
    "host": "irc.freenode.net",
    "follow_all": {
    },
    "port": 6697,
    "nick": "NICKNAME",
    "ssl": true,
    "channel": "#REPLACE"
}

irc,py:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from controler import DRingCtrl
from threading import Thread
import irc3
from irc3 import utils
import json
import sys


@irc3.plugin
class RingBridge:
    '''This plug-in react to event from IRC and send messages to IRC'''

    requires = [
        'irc3.plugins.core',
        'irc3.plugins.userlist',
        'irc3.plugins.command',
        'irc3.plugins.human',
    ]

    def __init__(self, bot):
        self.bot = bot
        self.log = self.bot.log
        self.channels = utils.as_list(self.bot.config.get('autojoins', []))
        self.config = None

    @irc3.event(irc3.rfc.PRIVMSG)
    def on_privmsg(self, mask=None, target=None, data=None, **kwargs):
        '''React to messages from IRC'''
        message = data.split(' ')
        follow_all = self.bot.config_bridge['follow_all']
        if message[0] == '!tell':
            msg_to_send = ('%s: ' % mask.nick) + ''.join(
                '%s ' % m for m in message[2:])[:-1]
            call_id = None
            users = self.bot.config_bridge['users']
            # Get ring_id from nick
            for ring_id in users.keys():
                if users[ring_id].lower() == message[1].lower():
                    call_id = ring_id
            if call_id:
                # if this nick want to receive all messages, we don't need
                # to send the message here
                if call_id not in follow_all.keys() \
                    or not follow_all[call_id] \
                        or target == self.bot.config_bridge['channel']:
                    print('Send: %s to %s' % (msg_to_send, call_id))
                    self.bot.controler.sendTextMessage(call_id,
                                                       msg_to_send)
            else:
                self.bot.privmsg(self.bot.config_bridge['channel'],
                                 'I don\'t know how to contact this person')
        # Send message to everyone who wants to receive message from #channel
        if target.lower() != self.bot.config_bridge['channel'].lower():
            return
        msg_to_send = ('%s: %s' % (mask.nick, data))
        for user in follow_all.keys():
            if follow_all[user]:
                self.bot.controler.sendTextMessage(user,
                                                   msg_to_send)

    @irc3.extend
    def send_message_to_irc(self, ring_id, message):
        '''send message to #channel'''
        self.bot.privmsg(self.bot.config_bridge['channel'],
                         '%s: %s' %
                         (self.bot.config_bridge['users'][ring_id], message),
                         True)


def threadClient(controler, config_bridge):
    config = dict(
        nick=config_bridge['nick'],
        autojoins=[config_bridge['channel']],
        host=config_bridge['host'],
        port=config_bridge['port'],
        ssl=config_bridge['ssl'],
        debug=config_bridge['debug'],
        includes=[__name__]
    )
    bot = irc3.IrcBot.from_config(config)
    # link irc and ring controlers
    bot.controler = controler
    bot.config_bridge = config_bridge
    controler.irc_client = bot

    bot.run(forever=True)


class IRCRingController(DRingCtrl):
    def __init__(self, config_bridge):
        super().__init__('ircbridge', True)
        self.irc_client = None
        self.config_bridge = config_bridge
        self.accountId = self.configurationmanager.getAccountList()[0]

    def updateConfig(self):
        '''Change actual config'''
        with open('config.json', 'w') as config_file:
            json.dump(self.config_bridge, config_file, indent=4)
        if self.irc_client:
            self.irc_client.config_bridge = self.config_bridge

    def onIncomingAccountMessage(self, accountId, fromAccount, payloads):
        '''React to message from Ring'''
        # Avoid to react to message from self.
        if fromAccount == self.accountId:
            return
        # If we have multiple accounts, avoid to react from another account
        if accountId != self.accountId:
            return
        message = '%s: %s' % (fromAccount, payloads['text/plain'])
        print('Receive new message from %s' % message)
        # React to !commands
        if payloads['text/plain'] == '!follow':
            self.config_bridge['follow_all'][fromAccount] = True
            self.updateConfig()
        elif payloads['text/plain'] == '!unfollow':
            self.config_bridge['follow_all'][fromAccount] = False
            self.updateConfig()
        elif payloads['text/plain'].split(' ')[0] == '!add':
            irc_nick = payloads['text/plain'].split(' ')[1]
            self.config_bridge['users'][fromAccount] = irc_nick
            self.updateConfig()
        elif payloads['text/plain'] == '!rm':
            del self.config_bridge['users'][fromAccount]
            del self.config_bridge['follow_all'][fromAccount]
            self.updateConfig()
        # Send message to IRC
        else:
            try:
                if self.irc_client:
                    self.irc_client.send_message_to_irc(fromAccount,
                                                        payloads['text/plain'])
            except:
                print('Can\'t read message received: %s' % payloads)

    def sendTextMessage(self, accountId, message):
        '''Send a message to a ring id'''
        if accountId == self.accountId:
            return
        self.configurationmanager.sendTextMessage(self.accountId,
                                                  accountId,
                                                  {
                                                    'text/plain':
                                                    str(message)
                                                  })


if __name__ == '__main__':
    config_bridge = None
    with open('config.json') as config_file:
        config_bridge = json.loads(config_file.read())
    if not config_bridge:
        print('Can\'t find config.json')
        sys.exit(-1)
    irc_controler = IRCRingController(config_bridge)
    thread = Thread(target=threadClient, args=(irc_controler, config_bridge,))
    thread.start()
    irc_controler.run()

LRC

Doxygen doc

The Doxygen documentation is available here and currently generated by Jenkins each week.

Database schema

lrcbd

CREATE TABLE profiles (id INTEGER PRIMARY KEY,  \
                       uri TEXT NOT NULL,       \
                       alias TEXT,              \
                       photo TEXT,              \
                       type TEXT,               \
                       status TEXT);

CREATE TABLE conversations (id INTEGER,\
                            participant_id INTEGER, \
                            FOREIGN KEY(participant_id) REFERENCES profiles(id));

CREATE TABLE interactions (id INTEGER PRIMARY KEY,\
                           account_id INTEGER, \
                           author_id INTEGER, \
                           conversation_id INTEGER, \
                           timestamp INTEGER, \
                           body TEXT,     \
                           type TEXT,  \
                           status TEXT, \
                           daemon_id TEXT, \
                           FOREIGN KEY(account_id) REFERENCES profiles(id), \
                           FOREIGN KEY(author_id) REFERENCES profiles(id), \
                           FOREIGN KEY(conversation_id) REFERENCES conversations(id));

CREATE TABLE profiles_accounts (profile_id INTEGER NOT NULL,                    \
                                account_id TEXT NOT NULL,                       \
                                is_account TEXT,                                \
                                FOREIGN KEY(profile_id) REFERENCES profiles(id));