Metadata-Version: 2.4
Name: pymultibots
Version: 0.0.6
Summary: Librairy to implement chatbot and broadcast bot on multiple platform (telegram, signal an whatsapp) at the same time
Author-email: Collective <pymultibot@systemli.org>
License: CC Attribution 4.0
Project-URL: Repository, https://gitlab.com/pymultibot/pymultibots
Keywords: chatbot,bot,telegram,signal,whatsapp
Requires-Python: >=3.8
Description-Content-Type: text/markdown

# Py multi-platform bot

## 1 - Presentation

This API is meant to help create a simple _non-AI_ multi-platform chatbot and difusion bot. It is meant to be a tool that can be used by people who aren't particullarly technically minded.

It will hopefully support as platforms :
 - The terminal (_for testing_)
 - Telegram
 - Signal (_will be implemented relatively quickly_)
 - Whatsapp (_will be implemented relatively quickly_)
 - A website (_no plan to implement yet_)

### 1.a - Installation

This repository is packaged on Pypi under the name `pymultibots`, and can thus be installed using :

```bash
pip install pymultibots
```

### 1.b - Example

An example is present at [example/](./example/), for now it can be run by using the command :

```bash
python3 terminal_example.py
```

An example of a telegram bot is also present in the same directory.

while being located in the [example/](./example/) folder.

## 2 - List of parameters

Parameters for the bot (dialogue, chat ID, etc...) are passed by `json` objects (or `dict` in the python code).

### 2.a - _proposed_ definition of dialogue bot

For the dialogue part of the bot, the dialogue can be described by this structure :

```json
{
	"start" : {
		"message" : "This bot is a simple bot asking you for your username and email",
		"no_response" : true,
		"next_state" : "username"
	},
	"username" : {
		"message" : "Please enter your username :",
		"store_key" : "uname",
		"input_restriction" : "max_length:10",
		"restriction_message" : "Your username can't be more than 10 characters long",
		"next_state" : "has_email"
	},
	"has_email" : {
		"message" : "Do you want to input an email ?",
		"options" : ["[Y] yes I want to input an email !", "[n] no I'd rather not !"],
		"option_key" : ["Y", "n"],
		"allow_different_key" : true,
		"next_state" : ["email", "end_state"],
		"default_state" : "end_state"
	},
	"email" : {
		"message" : "Please enter your email :",
		"store_key" : "email",
		"next_state" : "end_state"
	},
	"end_state" : {
		"message" : "Thank you for answering !",
		"end_state" : true,
		"cooldown" : 2
	}
}
```

This is passed to the _`to_add`_ function as a `dict` so it can be read of a `json` or `yml` file.

Each state is an entry in the `dict`, with the first state when interogating the bot being named `start`.

Here is a list of keys to parameterize the dialogue bot :
 - `"message"` (_`str`_) : The messages that will be sent.
 - `"file_paths"` (_`list`_ or _`str`_) : List of path of files or images if you want to join one to the message
 - `"no_response"` (_`bool`_) : If true, doesn't wait for a response to pass to the next state (thus `"next_state"` needs to be a state and not a list of states).
 - `"write_values"` (_`dict`_) : `dict` of values and associated keys to be written to the entry.
 - `"options"` (_`list`_ of _`str`_) : A list of options to be selected.
 - `"option_key"` (_`list`_ of _`str`_) : List of keys that correspond to each entry (for backend that requiere the user to choose an option by typing).
 - `"option_values"` (_`list`_) : Values associated to each option.
 - `"allow_different_key"` : If options are to be selected, wether to allow a key not in the list or not. Requires `default_state` to be set if `"next_state"` is a `list`.
 - `key_not_in_list_message` (_`str`_) : What message to send to the user if the key is not in the list and `"allow_different_key"` is set to false.
 - `"store_key"` (_`str`_) : Name of key where the input given by the user will be stored.
 - `"next_state"` (_`str`_ or _`list`_ of _`str`_) : Either the state that will come next, or a list of states that correspond to each option if `"options"` are passed.
 - `"default_state"` (_`str`_) : Next state if the option given is not in the list of `"options"`.
 - `"default_value"` : Default value to be store if the option given is not in the list of `"options"`.
 - `"end_state"` (_`bool`_) : If true this command will end the conversation.
 - `"input_restriction"` (_`str`_) : Restrictions to the user input : `int` to force the user to input an integer, `number` to force the user to input a number (floating point or not, separeted by `.` or `,`), `max_length:N` to prevent having more than `N` character. Multiple restriction can apply, separated by semicolons, like `int;max_length:10` to allow only an int with maximum of 10 characters. If `"options"` are passed, the restrictions onlly apply if the key given is not in `"option_key"` and `"allow_different_key"` is true.
 - `"restriction_message"` (_`str`_) : Message to be sent to the user if the input is restricted by `"input_restriction"`
 - `"cooldown"` (_`int`_) : Cooldown before being able to talk again to the bot.

There are some special keys that can be stored :
 - `"save"` (_`bool`_) : Wether the values gather by the dialogue with the user needs to be saved or not.
 - `"share"` (_`bool`_) : If `"share"` is set to anything but true, the state will be saved but not broadcasted if a broadcaster is running.
 - `"debug"` (_`bool`_) : If `"debug"` is set to true it won't be broadcasted, and although states marked with the `debug` flag are saved, they are deleted if and when the state file is archived.

All special keys (`"share"`, `"debug"`, `"cooldown"` and `"save"`) can be written without using the normal `"write_values"` option.

Every entry also have a field named `time` which denote the starting time of the dialogue as an iso timestamp. `time_str` is a string version of the `time` field according to the `time_format` parameter if present.

#### 2.a.1 - Multilanguage bot

You can get a multilanguage dialogue from merging dialogue defined in different languages using the `dialogue_utils.make_multilanguage_dialogue()` function :

```python
from pymultibots import dialogue_utils

dialogue = dialogue_utils.make_multilanguage_dialogue({
	"message" : "Welcome, please choose language !\n\n ¡Bienvenido! Elige el idioma.",
	"languages" : {
		"Francais" : french_language_dialogue,
		"Español" : spanish_language_dialogue
	}
})
```

You simply need to pass to the function a `dict` containing a starting message before language choice, and a dict of languages names associated with their dialogue dict as described in the previous section.

Any other key will be added to the newely generated `"start"` state, thus for example if you want your languages to be choses using short keys you can use :

```python
from pymultibots import dialogue_utils

dialogue = dialogue_utils.make_multilanguage_dialogue({
	"message" : "Welcome, please choose language !\n\n ¡Bienvenido! Elige el idioma.",
	"languages" : {
		"[fr] Francais" : french_language_dialogue,
		"[es] Español" : spanish_language_dialogue
	},
	"option_key" : ["fr", "es"]
})
```

### 2.b - _proposed_ list of particular key

Some keys have particular meaning :
 - `send` : If it is present and equal to anything but `true` (or `1`), the answer won't be sent (_if the bot has a broadcast component_).
 - `debug` : If it is present and equal to `true` or `1` the answer won't be archived when the list of answers are archived.

### 2.c - _proposed_ definition of broadcast bot

A dialogue bot can be joined with a broadcast bot which will format the broadcast text for each answer. Here is the typical structure of a broadcast definition :

```json
{
	"filename" : "json_state_folder/terminal_broadcast_state.json",
	"sleep_interval" : 1,
	"template" : [
		"Time of day {time_str},\n",
		{
			"template" : "username : {uname},\n",
			"default_template" : "No username given,\n"
		}, {
			"template" : "user email : {email}",
			"requiered_keys" : ["email"],
			"default_template" : "no email provided."
		}
	]
}
```

Here is a list of keys to parameterize the broadcaster bot :
 - `"filename"` (_`str`_) : Where the broadcaster saves the list of entries already seen.
 - `"sleep_interval"` (_`float`_) : How often in seconds to check whether new entries were added (default is 1 second).
 - `"template"` (_`str`_ or _`list`_ of _`dict`_ or _`str`_) : Template to create the message to broadcast, either a single `str` that gets formated using the keys stored by the bot, using the python function `template.format(entry)`, or a `list` of `dict` describing sections of the message as described bellow.

If the template field is made of a `list` of `dict`, each entry in the list are formated one after the other and appened to each other to generate the message, using the following field for each `dict` :
 - `"template"` (_`str`_) : Template to create the message to broadcast using the python function `template.format(entry)`
 - `"requiered_keys"` (_`list`_ of _`str`_) : List of keys that **all** need to be in the entry for the `"template_text"` to be formated and appened to the message.
 - `"default_template"` (_`str`_) : Template to create the message to broadcast using the python function `default_template.format(entry)` if the keys listed in `"requiered_keys"` are not **all** present in the entry.

### 2.d - _proposed_ parameters

```json
{
	"filename" : "json_state_folder/entry_list.json",
	"cancel_tag" : "cancel",
	"cancel_message" : "canceling the dialogue...",
	"timezone" : "utc",
	"time_format" : "%H:%M:%S %d/%m/%Y",
	"archive_dirpath" : "json_state_folder/archive/",
	"max_filesize_kb" : 0.5,
	"max_file_length" : 5,
	"max_archive_size_gb" : 0.00001
}
```

The bot can further be parameterized :
 - `"filename"` (_`str`_) : Path of where entries will be saved.
 - `"cancel_tag"` (_`str`_ or _`list`_ of _`str`_) : Tag or list of tag that if written in the answer to a question (case insensitive) will cancel the dialogue without cooldown.
 - `"cancel_message` (_`str`_) : Message that will be sent when canceling the dialogue.
 - `"timezone"` (_`str`_) : Timezone string (passed to `pytz.timezone`) forwhich the `time` field will be measured, default is `utc`.
 - `"time_format"` (_`str`_) : String that describe the format of the `time_str` field, according to the `strftime()` function of the `datetime` python package.
 - `"archive_dirpath"` (_`str`_) : Path of archive foler.
 - `"max_filesize_kb"` (_`float`_) : Maximum size of a file before being archived in `Kb`
 - `"max_file_length"` (_`int`_) : Maximum number of entry in a file before being archived.
 - `"max_archive_size_gb"` (_`float`_) : Maximum total archive folder size before the oldest archive file being deleted in `Gb`

### 2.e - _proposed_ connection to platforms

For each backend (except the terminal backend which doesn't use account) you can log in to the account that will be used by the bot and/or the broadcaster using the function `backend.log_in_account(account_params)` with `account_params` being a `dict` with the following fields (depending on each platform) :

#### 2.e.1 - _proposed_ connection to telegram

```python
from pymultibots.backends import telegram

backend = telegram.telegram_backend(backend_parameters)

backend.login_account({
	"telegram_chat_id" : "chat_id",
	"telegram_bot_token" : "bot_token",
	"force_option_key" : False,
	"proxy_url" : "127.0.0.1"
})
```

To connect to telegram you need to pass the following arguments :
 - `"telegram_chat_id"` ( _`str`_) : Id of the chat to which the broadcaster will broadcast.
 - `"telegram_bot_token"` (_`str`_) : Bot token.
 - `"force_option_key"` (_`bool`_, default is `false`) : Since telegram allows you to click on options, unless you set `"force_option_key"` to `true` the `"option_key"` parameter will be ignored so that the user can simply click rather than write an input by hand.
 - `"proxy_url"` (_`str`_) : Optional proxy through which the bot will interact with the telegram servers.

#### 2.e.2 - _proposed_ connection to signal

```python
from pymultibots.backends import signal

backend = signal.signal_backend(backend_parameters)

backend.login_account({
	"todo" : "todo"
})
```

To connect to signal you need to pass the following arguments :
 - _Todo_

#### 2.e.2 - _proposed_ connection to whatsapp

```python
from pymultibots.backends import whatsapp

backend = whatsapp.whatsapp_backend(backend_parameters)

backend.login_account({
	"todo" : "todo"
})
```

To connect to whatsapp you need to pass the following arguments :
 - _Todo_
